diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7517175d..d0f2a6a0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,4 +21,6 @@ jobs: - run: npm publish -w=blinkdb continue-on-error: true - run: npm publish -w=@blinkdb/react + continue-on-error: true + - run: npm publish -w=@blinkdb/vue continue-on-error: true \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 28f17833..5c7dbad9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -558,9 +558,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", - "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", "bin": { "parser": "bin/babel-parser.js" }, @@ -838,6 +838,10 @@ "resolved": "packages/react", "link": true }, + "node_modules/@blinkdb/vue": { + "resolved": "packages/vue", + "link": true + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1776,9 +1780,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.15", @@ -2338,6 +2342,24 @@ "react-dom": "^18.0.0" } }, + "node_modules/@testing-library/vue": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/vue/-/vue-7.0.0.tgz", + "integrity": "sha512-JU/q93HGo2qdm1dCgWymkeQlfpC0/0/DBZ2nAHgEAsVZxX11xVIxT7gbXdI7HACQpUbsUWt1zABGU075Fzt9XQ==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0", + "@testing-library/dom": "^9.0.1", + "@vue/test-utils": "^2.3.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@vue/compiler-sfc": ">= 3", + "vue": ">= 3" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -2693,12 +2715,195 @@ "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.13.tgz", "integrity": "sha512-A3uY356uOU9nGa+TQIT/i3ziWUgJjVMUrGGXSrtRiTwklyCFjGVWIOHoEIHbJpiyhDkJd9kvIWUOfXK1IkK8XQ==" }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "peer": true, + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/compiler-sfc/node_modules/magic-string": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "peer": true, + "dependencies": { + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "node_modules/@vue/reactivity-transform/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/reactivity-transform/node_modules/magic-string": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "peer": true, + "dependencies": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "peer": true, + "dependencies": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "peer": true, + "dependencies": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + }, + "peerDependencies": { + "vue": "3.3.4" + } + }, + "node_modules/@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==", + "peer": true + }, + "node_modules/@vue/test-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.0.tgz", + "integrity": "sha512-BKB9aj1yky63/I3IwSr1FjUeHYsKXI7D6S9F378AHt7a5vC0dLkOBtSsFXoRGC/7BfHmiB9HRhT+i9xrUHoAKw==", + "dev": true, + "dependencies": { + "js-beautify": "1.14.6", + "vue-component-type-helpers": "1.6.5" + }, + "peerDependencies": { + "@vue/compiler-dom": "^3.0.1", + "@vue/server-renderer": "^3.0.1", + "vue": "^3.0.1" + }, + "peerDependenciesMeta": { + "@vue/compiler-dom": { + "optional": true + }, + "@vue/server-renderer": { + "optional": true + } + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -3977,6 +4182,16 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -4460,6 +4675,52 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "dev": true, + "dependencies": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "bin": { + "editorconfig": "bin/editorconfig" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/editorconfig/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/editorconfig/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/editorconfig/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, "node_modules/electron-to-chromium": { "version": "1.4.384", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.384.tgz", @@ -5545,6 +5806,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/inline-style-parser": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", @@ -7281,6 +7548,66 @@ "@types/yargs-parser": "*" } }, + "node_modules/js-beautify": { + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.6.tgz", + "integrity": "sha512-GfofQY5zDp+cuHc+gsEXKPpNw2KbPddreEo35O6jT6i0RVK6LhsoYBhq5TvK4/n74wnA0QbK8gGd+jUZwTMKJw==", + "dev": true, + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^0.15.3", + "glob": "^8.0.3", + "nopt": "^6.0.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9057,6 +9384,21 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9858,6 +10200,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -10565,6 +10919,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==", + "dev": true + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -11792,6 +12152,25 @@ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==" }, + "node_modules/vue": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/vue-component-type-helpers": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.6.5.tgz", + "integrity": "sha512-iGdlqtajmiqed8ptURKPJ/Olz0/mwripVZszg6tygfZSIL9kYFPJTNY6+Q6OjWGznl2L06vxG5HvNvAnWrnzbg==", + "dev": true + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -12300,6 +12679,18 @@ "blinkdb": "^0.13.0", "react": "^18.0.0" } + }, + "packages/vue": { + "name": "@blinkdb/vue", + "version": "0.0.1", + "license": "MIT", + "devDependencies": { + "@testing-library/vue": "^7.0.0" + }, + "peerDependencies": { + "blinkdb": "^0.13.0", + "vue": "^3.0.0" + } } }, "dependencies": { @@ -12705,9 +13096,9 @@ } }, "@babel/parser": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", - "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==" + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==" }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -12968,6 +13359,12 @@ "react-test-renderer": "^18.2.0" } }, + "@blinkdb/vue": { + "version": "file:packages/vue", + "requires": { + "@testing-library/vue": "^7.0.0" + } + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -13616,9 +14013,9 @@ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "@jridgewell/trace-mapping": { "version": "0.3.15", @@ -14001,6 +14398,17 @@ "@types/react-dom": "^18.0.0" } }, + "@testing-library/vue": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/vue/-/vue-7.0.0.tgz", + "integrity": "sha512-JU/q93HGo2qdm1dCgWymkeQlfpC0/0/DBZ2nAHgEAsVZxX11xVIxT7gbXdI7HACQpUbsUWt1zABGU075Fzt9XQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.21.0", + "@testing-library/dom": "^9.0.1", + "@vue/test-utils": "^2.3.1" + } + }, "@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -14354,12 +14762,179 @@ "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.13.tgz", "integrity": "sha512-A3uY356uOU9nGa+TQIT/i3ziWUgJjVMUrGGXSrtRiTwklyCFjGVWIOHoEIHbJpiyhDkJd9kvIWUOfXK1IkK8XQ==" }, + "@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "peer": true, + "requires": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + } + } + }, + "@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "peer": true, + "requires": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "peer": true, + "requires": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "magic-string": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "peer": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + } + } + }, + "@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "peer": true, + "requires": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "peer": true, + "requires": { + "@vue/shared": "3.3.4" + } + }, + "@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "peer": true, + "requires": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + }, + "dependencies": { + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "magic-string": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "peer": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + } + } + }, + "@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "peer": true, + "requires": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "peer": true, + "requires": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "peer": true, + "requires": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==", + "peer": true + }, + "@vue/test-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.0.tgz", + "integrity": "sha512-BKB9aj1yky63/I3IwSr1FjUeHYsKXI7D6S9F378AHt7a5vC0dLkOBtSsFXoRGC/7BfHmiB9HRhT+i9xrUHoAKw==", + "dev": true, + "requires": { + "js-beautify": "1.14.6", + "vue-component-type-helpers": "1.6.5" + } + }, "abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -15232,6 +15807,16 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -15583,6 +16168,48 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } + }, "electron-to-chromium": { "version": "1.4.384", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.384.tgz", @@ -16375,6 +17002,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "inline-style-parser": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", @@ -17715,6 +18348,51 @@ } } }, + "js-beautify": { + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.6.tgz", + "integrity": "sha512-GfofQY5zDp+cuHc+gsEXKPpNw2KbPddreEo35O6jT6i0RVK6LhsoYBhq5TvK4/n74wnA0QbK8gGd+jUZwTMKJw==", + "dev": true, + "requires": { + "config-chain": "^1.1.13", + "editorconfig": "^0.15.3", + "glob": "^8.0.3", + "nopt": "^6.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -18896,6 +19574,15 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" }, + "nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "requires": { + "abbrev": "^1.0.0" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -19411,6 +20098,18 @@ "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==" }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -19931,6 +20630,12 @@ "object-inspect": "^1.9.0" } }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==", + "dev": true + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -20769,6 +21474,25 @@ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==" }, + "vue": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "peer": true, + "requires": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "vue-component-type-helpers": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.6.5.tgz", + "integrity": "sha512-iGdlqtajmiqed8ptURKPJ/Olz0/mwripVZszg6tygfZSIL9kYFPJTNY6+Q6OjWGznl2L06vxG5HvNvAnWrnzbg==", + "dev": true + }, "w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/package.json b/package.json index aefc8c55..faa855f4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ ], "scripts": { "prepare": "node ./prepare.js", - "build": "npm run build -w=blinkdb -w=@blinkdb/react", + "build": "npm run build -w=blinkdb -w=@blinkdb/react -w=@blinkdb/vue", "test": "jest", "test-coverage": "jest --coverage" }, diff --git a/packages/docs/.astro/types.d.ts b/packages/docs/.astro/types.d.ts index 90cebef5..febec16f 100644 --- a/packages/docs/.astro/types.d.ts +++ b/packages/docs/.astro/types.d.ts @@ -171,6 +171,13 @@ declare module "astro:content" { collection: "docs"; data: InferEntrySchema<"docs">; } & { render(): Render[".mdx"] }; + "vue.mdx": { + id: "vue.mdx"; + slug: "vue"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs">; + } & { render(): Render[".mdx"] }; }; reference: { "BlinkDbProvider.mdx": { @@ -236,6 +243,13 @@ declare module "astro:content" { collection: "reference"; data: InferEntrySchema<"reference">; } & { render(): Render[".mdx"] }; + "isAction.mdx": { + id: "isAction.mdx"; + slug: "isaction"; + body: string; + collection: "reference"; + data: InferEntrySchema<"reference">; + } & { render(): Render[".mdx"] }; "isValidEntity.mdx": { id: "isValidEntity.mdx"; slug: "isvalidentity"; @@ -376,6 +390,27 @@ declare module "astro:content" { collection: "reference"; data: InferEntrySchema<"reference">; } & { render(): Render[".mdx"] }; + "watchFirst.mdx": { + id: "watchFirst.mdx"; + slug: "watchfirst"; + body: string; + collection: "reference"; + data: InferEntrySchema<"reference">; + } & { render(): Render[".mdx"] }; + "watchMany.mdx": { + id: "watchMany.mdx"; + slug: "watchmany"; + body: string; + collection: "reference"; + data: InferEntrySchema<"reference">; + } & { render(): Render[".mdx"] }; + "watchOne.mdx": { + id: "watchOne.mdx"; + slug: "watchone"; + body: string; + collection: "reference"; + data: InferEntrySchema<"reference">; + } & { render(): Render[".mdx"] }; }; }; diff --git a/packages/docs/src/components/framework-select.tsx b/packages/docs/src/components/framework-select.tsx index e77a97f7..e43fe7fa 100644 --- a/packages/docs/src/components/framework-select.tsx +++ b/packages/docs/src/components/framework-select.tsx @@ -1,5 +1,5 @@ import { Listbox, Transition } from '@headlessui/react'; -import { TbBrandTypescript, TbBrandReact } from "react-icons/tb/index.js"; +import { TbBrandTypescript, TbBrandReact, TbBrandVue } from "react-icons/tb/index.js"; const ChevronUpDownIcon = (props: { className: string }) => { return ( @@ -27,6 +27,11 @@ const frameworks = { name: "React", href: "/docs/reference/react", icon: + }, + 'vue': { + name: "Vue", + href: "/docs/reference/vue", + icon: } } as const; diff --git a/packages/docs/src/content/config.ts b/packages/docs/src/content/config.ts index 5b6563ee..3b086089 100644 --- a/packages/docs/src/content/config.ts +++ b/packages/docs/src/content/config.ts @@ -4,7 +4,7 @@ export const collections = { reference: defineCollection({ schema: z.object({ title: z.string(), - package: z.optional(z.enum(["blinkdb", "@blinkdb/react"])), + package: z.optional(z.enum(["blinkdb", "@blinkdb/react", "@blinkdb/vue"])), secondary: z.optional(z.boolean()), }), }), diff --git a/packages/docs/src/content/docs/react.mdx b/packages/docs/src/content/docs/react.mdx index aa7348bd..71874dfa 100644 --- a/packages/docs/src/content/docs/react.mdx +++ b/packages/docs/src/content/docs/react.mdx @@ -135,6 +135,7 @@ the current state of the query. ```tsx import { useTable, useOne } from '@blinkdb/react'; import { Model } from './model.ts'; +import { UserCard } from './UserCard.tsx'; const Component = () => { const userTable = useTable((model: Model) => model.users); @@ -155,7 +156,7 @@ const Component = () => { The object returned from the queries is of type `QueryResult`, and looks like this: ```ts -type QueryResult { +interface QueryResult { // Retrieved data if the query has loaded items data: T|undefined; // An error object if the query has produced an error diff --git a/packages/docs/src/content/docs/vue.mdx b/packages/docs/src/content/docs/vue.mdx new file mode 100644 index 00000000..50f06fc5 --- /dev/null +++ b/packages/docs/src/content/docs/vue.mdx @@ -0,0 +1,80 @@ +--- +title: "Vue" +--- + +# Vue + +You can use blinkDB in Vue the same way you would use it without a framework. +You can create tables, register hooks, and use all the [CRUD functions](/docs/crud) +the same way you use them in normal Typescript. + +For retrieving items from the database, the [`@blinkdb/vue`](https://npmjs.com/package/@blinkdb/vue) package provides +reactive hooks that integrate better into Vue. + +## Querying items + +Typically, in Vue components, you do not want to call a function manually to retrieve items from the database, but +instead to keep an up-to-date view of items in the database. In `@blinkdb/vue`, you can achieve this with the +[`watchFirst()`](/docs/reference/vue/watchfirst), [`watchMany()`](/docs/reference/vue/watchmany), and [`watchOne()`](/docs/reference/vue/watchone) reactive hooks. + +```vue + + + +``` + +In contrast to their counterparts [`first()`](/docs/reference/first), [`many()`](/docs/reference/many), and [`one()`](/docs/reference/one), +these hooks do not return a value directly. Instead, they return a `QueryResult` object, which looks like this: + +```ts +interface QueryResult { + // Retrieved data if the query has loaded items yet + data: T|undefined; + // An error object if the query has produced an error + error: Error|undefined; + // The current state of the query + state: "loading"|"done"|"error"; +} +``` + +This allows the reactive hooks to retrieve data asynchronously from the database. + +### Loading states + +The `QueryResult` object returned from these reactive hooks contains a `state` property that reflects +whether the query has finished loading yet. Likewise, the `error` property contains any error that may have +occurred while executing the query. + +```vue + + + +``` \ No newline at end of file diff --git a/packages/docs/src/content/reference/useFirst.mdx b/packages/docs/src/content/reference/useFirst.mdx index 3c3541ea..38b7d8ab 100644 --- a/packages/docs/src/content/reference/useFirst.mdx +++ b/packages/docs/src/content/reference/useFirst.mdx @@ -11,7 +11,7 @@ Retrieves the first entity from the table that matches the given filter, or `nul const Component = () => { const userTable = useTable((model: Model) => model.users); // Retrieve the first user named 'Alice' - const { data: firstUser } = await useFirst(userTable, { + const { data: firstUser } = useFirst(userTable, { where: { name: "Alice" } @@ -27,11 +27,11 @@ const Component = () => { ## Query State -`useFirst()` is asynchronous, which means the query doesn't return your values immediately. +`useFirst()` is asynchronous, which means the queried data isn't available immediately. Instead, it returns an object containing the current query state, the data (if present), and zero or one error (if your query threw an error). ```ts -type QueryResult { +interface QueryResult { // The item if successful, // `null` if no entity matched, // `undefined` if the query is not done yet @@ -48,7 +48,7 @@ You can use the loading state to display loading indicators while you wait for y ```tsx const Component = () => { const userTable = useTable((model: Model) => model.users); - const { data: firstUser, state, error } = await useFirst(userTable, { + const { data: firstUser, state, error } = useFirst(userTable, { where: { name: "Alice" } @@ -70,7 +70,7 @@ If no second parameter is supplied, `useFirst()` will return the first item in t ```ts // Retrieve the first user -const { data: firstUser } = await useFirst(userTable); +const { data: firstUser } = useFirst(userTable); ``` What exactly is meant by the "first" item in the table depends upon the insertion order, so the result is not exactly @@ -83,7 +83,7 @@ if no such entity exists. ```ts // Retrieve the user with the "alice-uuid" uuid -const { data: alice } = await useFirst(userTable, "alice-uuid"); +const { data: alice } = useFirst(userTable, "alice-uuid"); ``` ## Retrieve first item that matches a filter @@ -93,7 +93,7 @@ no item in the table matches the filter). ```ts // Retrieve the first user named 'Alice' -const { data: firstUser } = await useFirst(userTable, { +const { data: firstUser } = useFirst(userTable, { where: { name: "Alice" } diff --git a/packages/docs/src/content/reference/useMany.mdx b/packages/docs/src/content/reference/useMany.mdx index 35922e29..a0be6ad2 100644 --- a/packages/docs/src/content/reference/useMany.mdx +++ b/packages/docs/src/content/reference/useMany.mdx @@ -12,7 +12,7 @@ return matching items - if not given, all items are returned. const Component = () => { const userTable = useTable((model: Model) => model.users); // Retrieve all users named Bob - const { data: bobUsers } = await useMany(userTable, { + const { data: bobUsers } = useMany(userTable, { where: { name: "Bob" } @@ -28,11 +28,11 @@ const Component = () => { ## Query State -`useMany()` is asynchronous, which means the query doesn't return your values immediately. +`useMany()` is asynchronous, which means the queried data isn't available immediately. Instead, it returns an object containing the current query state, the data (if present), and zero or one error (if your query threw an error). ```ts -type QueryResult { +interface QueryResult { // The items if successful, // or `undefined` if the query is not done yet data: T[]|undefined; @@ -48,7 +48,7 @@ You can use the loading state to display loading indicators while you wait for y ```tsx const Component = () => { const userTable = useTable((model: Model) => model.users); - const { data: users, state, error } = await useMany(userTable); + const { data: users, state, error } = useMany(userTable); return ( <> @@ -68,7 +68,7 @@ If no second parameter is supplied, `useMany()` will return all items in the tab ```ts // Retrieve all users -const { data: users } = await useMany(userTable); +const { data: users } = useMany(userTable); ``` ## Retrieve items that match a filter @@ -77,7 +77,7 @@ If a [filter](/docs/filters) is provided as the second parameter, `useMany()` re ```ts // Retrieve all users named Bob -const { data: bobUsers } = await useMany(userTable, { +const { data: bobUsers } = useMany(userTable, { where: { name: "Bob" } diff --git a/packages/docs/src/content/reference/useOne.mdx b/packages/docs/src/content/reference/useOne.mdx index de02ee60..b1e3729b 100644 --- a/packages/docs/src/content/reference/useOne.mdx +++ b/packages/docs/src/content/reference/useOne.mdx @@ -18,7 +18,7 @@ This makes it perfect for retrieving entities by their primary id, which are uni const Component = () => { const userTable = useTable((model: Model) => model.users); // Retrieve alice by UUID - const { data: alice } = await one(userTable, "alice-uuid"); + const { data: alice } = useOne(userTable, "alice-uuid"); ... } ``` @@ -30,11 +30,11 @@ const Component = () => { ## Query State -`useOne()` is asynchronous, which means the query doesn't return your values immediately. +`useOne()` is asynchronous, which means the queried data isn't available immediately. Instead, it returns an object containing the current query state, the data (if present), and zero or one error (if your query threw an error). ```ts -type QueryResult { +interface QueryResult { // The item if successful, // or `undefined` if the query is not done yet data: T|undefined; @@ -51,7 +51,7 @@ You can use the loading state to display loading indicators while you wait for y ```tsx const Component = () => { const userTable = useTable((model: Model) => model.users); - const { data: alice } = await one(userTable, "alice-uuid"); + const { data: alice } = useOne(userTable, "alice-uuid"); return ( <> @@ -70,7 +70,7 @@ or return an error as detailed above. ```ts // Retrieve alice by UUID -const { data: alice } = await one(userTable, "alice-uuid"); +const { data: alice } = useOne(userTable, "alice-uuid"); ``` ## Retrieve by filter @@ -80,7 +80,7 @@ or return an error if no / more than one item is available. ```ts // Retrieve alice by UUID (with filter this time!) -const { data: alice } = await one(userTable, { +const { data: alice } = useOne(userTable, { where: { id: "alice-uuid" } diff --git a/packages/docs/src/content/reference/watchFirst.mdx b/packages/docs/src/content/reference/watchFirst.mdx new file mode 100644 index 00000000..46413ef4 --- /dev/null +++ b/packages/docs/src/content/reference/watchFirst.mdx @@ -0,0 +1,104 @@ +--- +title: "watchFirst()" +package: "@blinkdb/vue" +--- + +# watchFirst() + +Retrieves the first entity from the table that matches the given filter, or `null` if no entity matches. + +```vue + + + +``` + +| Parameter | Description | +|--------------|-------------------------------------------------------------------------------------------------------------------------------------------| +| `table` | A table created by [`createTable()`](createtable) . | +| `filterOrId` | Optional. Can be either a filter or primary id. If given, the item returned will be the first one to match. See [filters](/docs/filters). | + +## Query State + +`watchFirst()` is asynchronous, which means the queried data isn't available immediately. +Instead, it returns an object containing the current query state, the data (if present), and zero or one error (if your query threw an error). + +The query starts out in the `loading` state, fetches the items, and then continues to the `done` state if items were +successfully retrieved, or to the `error` state if retrieving the items caused an error. + +```ts +interface QueryResult { + // The item if successful, + // `null` if no entity matched, + // `undefined` if the query is not done yet + data: T|null|undefined; + // An error object if the query has produced an error + error: Error|undefined; + // The current state of the query + state: "loading"|"done"|"error"; +} +``` + +You can use the additional information to display error & loading indicators while you wait for your query to complete: + +```vue + + + +``` + +## Retrieve first item in table + +If no second parameter is supplied, `watchFirst()` will return the first item in the table. + +```ts +// Retrieve the first user +const { data: firstUser } = watchFirst(userTable); +``` + +What exactly is meant by the "first" item in the table depends upon the insertion order, so the result is not exactly +deterministic. Still, this is sometimes useful for tables that only ever store zero or one entity. + +## Retrieve item by primary id + +If an id is given as the second parameter, `watchFirst()` will return either the entity with a matching primary id or `null` +if no such entity exists. + +```ts +// Retrieve the user with the "alice-uuid" uuid, if they exist +const { data: alice } = watchFirst(userTable, "alice-uuid"); +``` + +## Retrieve first item that matches a filter + +If a [filter](/docs/filters) is provided as the second parameter, `watchFirst()` returns the first item that matches the filter (or `null` if +no item in the table matches the filter). + +```ts +// Retrieve the first user named 'Alice' +const { data: firstUser } = watchFirst(userTable, { + where: { name: "Alice" } +}); +``` \ No newline at end of file diff --git a/packages/docs/src/content/reference/watchMany.mdx b/packages/docs/src/content/reference/watchMany.mdx new file mode 100644 index 00000000..8bd369c6 --- /dev/null +++ b/packages/docs/src/content/reference/watchMany.mdx @@ -0,0 +1,93 @@ +--- +title: "watchMany()" +package: "@blinkdb/vue" +--- + +# watchMany() + +Retrieves entities from the table. If a [filter](/docs/filters) is supplied, blinkDB will only +return matching items - if not given, all items are returned. + +```vue + + + +``` + +| Parameter | Description | +| --------- | --------------------------------------------------------------------------------------- | +| `table` | The table created by [`createTable()`](createtable) . | +| `filter` | Optional. If given, items returned will match the filter. See [filters](/docs/filters). | + +## Query State + +`watchMany()` is asynchronous, which means the queried data isn't available immediately. +Instead, it returns an object containing the current query state, the data (if present), and zero or one error (if your query threw an error). + +The query starts out in the `loading` state, fetches the items, and then continues to the `done` state if items were +successfully retrieved, or to the `error` state if retrieving the items caused an error. + +```ts +interface QueryResult { + // The items if successful, + // or `undefined` if the query is not done yet + data: T[]|undefined; + // An error object if the query has produced an error + error: Error|undefined; + // The current state of the query + state: "loading"|"done"|"error"; +} +``` + +You can use the additional information to display error & loading indicators while you wait for your query to complete: + +```vue + + + +``` + +## Retrieve all items + +If no second parameter is supplied, `watchMany()` will return all items in the table. + +```ts +// Retrieve all users +const { data: users } = watchMany(userTable); +``` + +## Retrieve items that match a filter + +If a [filter](/docs/filters) is provided as the second parameter, `watchMany()` returns all items that match the filter. + +```ts +// Retrieve all users named Bob +const { data: bobUsers } = watchMany(userTable, { + where: { name: "Bob" } +}); +``` diff --git a/packages/docs/src/content/reference/watchOne.mdx b/packages/docs/src/content/reference/watchOne.mdx new file mode 100644 index 00000000..5983b9ff --- /dev/null +++ b/packages/docs/src/content/reference/watchOne.mdx @@ -0,0 +1,95 @@ +--- +title: "watchOne()" +package: "@blinkdb/vue" +--- + +# watchOne() + +Retrieves one entity from the table. + +This is similar to [`watchFirst()`](watchfirst) in the sense that it retrieves only the first matching entity, but: +- it returns an error if no entity was found +- it returns an error if more than one entity matched the filter + +This makes it perfect for retrieving entities by their primary id, which should be unique by default. + +```vue + + + +``` + +| Parameter | Description | +|--------------|-----------------------------------------------------------------------------------------------------------| +| `table` | The table created by [`createTable()`](createtable) . | +| `filterOrId` | Either a filter or an id. The item returned will be the first one to match. See [filters](/docs/filters). | + +## Query State + +`watchOne()` is asynchronous, which means the queried data isn't available immediately. +Instead, it returns an object containing the current query state, the data (if present), and zero or one error (if your query threw an error). + +The query starts out in the `loading` state, fetches the items, and then continues to the `done` state if items were +successfully retrieved, or to the `error` state if retrieving the items caused an error. + +```ts +interface QueryResult { + // The item if successful, + // or `undefined` if the query is not done yet + data: T|undefined; + // An error object if the query has produced an error + // (if no item has been found, of if more than one item was found) + error: Error|undefined; + // The current state of the query + state: "loading"|"done"|"error"; +} +``` + +You can use the additional information to display error & loading indicators while you wait for your query to complete: + +```vue + + + +``` + +## Retrieve by primary id + +In case an id is given as the second parameter, `watchOne()` will return either the entity with a matching primary id, +or return an error as detailed above. + +```ts +// Retrieve alice by UUID +const { data: alice } = watchOne(userTable, "alice-uuid"); +``` + +## Retrieve by filter + +If the second parameter is a [filter](/docs/filters), `useOne()` returns the item that matches the filter, +or return an error if no / more than one item is available. + +```ts +// Retrieve alice by UUID (with filter this time!) +const { data: alice } = watchOne(userTable, { + where: { + id: "alice-uuid" + } +}); +``` \ No newline at end of file diff --git a/packages/docs/src/pages/docs/[...slug].astro b/packages/docs/src/pages/docs/[...slug].astro index 25563f5c..a4138d91 100644 --- a/packages/docs/src/pages/docs/[...slug].astro +++ b/packages/docs/src/pages/docs/[...slug].astro @@ -53,7 +53,8 @@ const guideEntries = sortAccordingTo(entries, [ ]); const frameworkEntries = sortAccordingTo(entries, [ - "react" + "react", + "vue" ]); const prevGuideEntry = entryWithOffset(guideEntries, entry, -1); diff --git a/packages/docs/src/pages/docs/reference/[...slug].astro b/packages/docs/src/pages/docs/reference/[...slug].astro index 0f0ea4be..aa67df5e 100644 --- a/packages/docs/src/pages/docs/reference/[...slug].astro +++ b/packages/docs/src/pages/docs/reference/[...slug].astro @@ -6,7 +6,7 @@ import Code from '../../../components/md/code.astro'; import H2 from '../../../components/md/h2.astro'; import SidebarGroup from '../../../components/sidebar-group.astro'; import HeadingList from '../../../components/heading-list.astro'; -import { FrameworkSelect } from '../../../components/framework-select'; +import { FrameworkId, FrameworkSelect } from "../../../components/framework-select"; import CodeBlock from '../../../components/code-block.astro'; import Hr from "../../../components/md/hr.astro"; @@ -34,6 +34,7 @@ export async function getStaticPaths() { ...blogEntries.filter(isInPackage(['blinkdb'])).map((entry, _, entries) => mapToPath(entry, entries)), // React ...blogEntries.filter(isInPackage(['blinkdb', '@blinkdb/react'])).map((entry, _, entries) => mapToPath(entry, entries, 'react')), + ...blogEntries.filter(isInPackage(['blinkdb', '@blinkdb/vue'])).map((entry, _, entries) => mapToPath(entry, entries, 'vue')), ]; } @@ -46,6 +47,14 @@ const { pathname } = Astro.url; const { entry, entries } = Astro.props; const { Content, headings } = await entry.render(); const sections = entries.map(e => e.data.package ?? "blinkdb").reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], []); +sections.sort(); + +let frameworkId: FrameworkId = ''; +if(pathname.includes('react')) { + frameworkId = 'react'; +} else if(pathname.includes('vue')) { + frameworkId = 'vue'; +} --- @@ -68,7 +77,7 @@ const sections = entries.map(e => e.data.package ?? "blinkdb").reduce(
- + {sections.map(section => ( {entries.filter(e => e.data.package === section && !e.data.secondary).map(e => ( diff --git a/packages/react/readme.md b/packages/react/readme.md index 0b533c7b..50e464b3 100644 --- a/packages/react/readme.md +++ b/packages/react/readme.md @@ -11,13 +11,13 @@ ```tsx const Component = () => { - const userTable = useTable((model: Model) => model.users); - const { data: firstUser } = await useMany(userTable, { + const { data: users } = useMany(userTable, { where: { name: { in: ["Alice", "Charlie"] }, age: { gt: 24 }, }, }); + ... } ``` diff --git a/packages/react/src/retrieval/useFirst.ts b/packages/react/src/retrieval/useFirst.ts index e54bd0c6..4bc81b17 100644 --- a/packages/react/src/retrieval/useFirst.ts +++ b/packages/react/src/retrieval/useFirst.ts @@ -7,7 +7,7 @@ import { useMany } from "./useMany"; * * @example * // Retrieve the first user - * const { data: firstUser } = await useFirst(userTable); + * const { data: firstUser } = useFirst(userTable); */ export function useFirst, P extends PrimaryKeyOf>( table: Table @@ -18,7 +18,7 @@ export function useFirst, P extends PrimaryKeyOf>( * * @example * // Retrieve the first user named 'Alice' - * const { data: firstUser } = await useFirst(userTable, { + * const { data: firstUser } = useFirst(userTable, { * where: { * name: "Alice" * } @@ -34,7 +34,7 @@ export function useFirst, P extends PrimaryKeyOf>( * * @example * // Retrieve the 'Alice' user by their id - * const { data: firstUser } = await useFirst(userTable, 'alice-uuid'); + * const { data: firstUser } = useFirst(userTable, 'alice-uuid'); */ export function useFirst, P extends PrimaryKeyOf>( table: Table, @@ -47,16 +47,19 @@ export function useFirst, P extends PrimaryKeyOf>( ): QueryResult { const primaryKeyProperty = key(table); let result: QueryResult; - if(queryOrId === undefined) { + if (queryOrId === undefined) { result = useMany(table); } else { - const query = typeof queryOrId === "object" ? queryOrId : { where: { [primaryKeyProperty]: queryOrId } } as unknown as Query; + const query = + typeof queryOrId === "object" + ? queryOrId + : ({ where: { [primaryKeyProperty]: queryOrId } } as unknown as Query); result = useMany(table, query); } return { ...result, - data: result.data ? (result.data[0] ?? null) : undefined, - error: undefined + data: result.data ? result.data[0] ?? null : undefined, + error: undefined, } as QueryResult; -} \ No newline at end of file +} diff --git a/packages/react/src/retrieval/useOne.ts b/packages/react/src/retrieval/useOne.ts index 4a927854..43662ca1 100644 --- a/packages/react/src/retrieval/useOne.ts +++ b/packages/react/src/retrieval/useOne.ts @@ -1,4 +1,12 @@ -import { Entity, ItemNotFoundError, key, MoreThanOneItemFoundError, PrimaryKeyOf, Query, Table } from "blinkdb"; +import { + Entity, + ItemNotFoundError, + key, + MoreThanOneItemFoundError, + PrimaryKeyOf, + Query, + Table, +} from "blinkdb"; import { QueryResult } from "./types"; import { useMany } from "./useMany"; @@ -9,7 +17,7 @@ import { useMany } from "./useMany"; * * @example * // Retrieve the only user named 'Alice' - * const { data: aliceUser } = await useOne(userTable, { + * const { data: aliceUser } = useOne(userTable, { * where: { * name: "Alice" * } @@ -27,7 +35,7 @@ export function useOne, P extends PrimaryKeyOf>( * * @example * // Retrieve the 'Alice' user by their id - * const { data: aliceUser } = await useOne(userTable, 'alice-uuid'); + * const { data: aliceUser } = useOne(userTable, 'alice-uuid'); */ export function useOne, P extends PrimaryKeyOf>( table: Table, @@ -40,31 +48,34 @@ export function useOne, P extends PrimaryKeyOf>( ): QueryResult { const primaryKeyProperty = key(table); let result: QueryResult; - if(queryOrId === undefined) { + if (queryOrId === undefined) { result = useMany(table); } else { - const query = typeof queryOrId === "object" ? queryOrId : { where: { [primaryKeyProperty]: queryOrId } } as unknown as Query; + const query = + typeof queryOrId === "object" + ? queryOrId + : ({ where: { [primaryKeyProperty]: queryOrId } } as unknown as Query); result = useMany(table, query); } - if(result.state === "done") { + if (result.state === "done") { if (result.data.length === 0) { return { state: "error", data: undefined, - error: new ItemNotFoundError(queryOrId) - } + error: new ItemNotFoundError(queryOrId), + }; } else if (result.data.length > 1) { return { state: "error", data: undefined, - error: new MoreThanOneItemFoundError(queryOrId) - } + error: new MoreThanOneItemFoundError(queryOrId), + }; } } return { ...result, - data: result.data ? result.data[0] : undefined + data: result.data ? result.data[0] : undefined, } as QueryResult; -} \ No newline at end of file +} diff --git a/packages/vue/jest.config.ts b/packages/vue/jest.config.ts new file mode 100644 index 00000000..ecd77328 --- /dev/null +++ b/packages/vue/jest.config.ts @@ -0,0 +1,14 @@ +import type { Config } from "jest"; + +const config: Config = { + testEnvironment: "jsdom", + testEnvironmentOptions: { + customExportConditions: ["node", "node-addons"], + }, + transformIgnorePatterns: [], + transform: { + "^.+\\.(t|j)sx?$": "@swc/jest", + }, +}; + +export default config; diff --git a/packages/vue/package.json b/packages/vue/package.json new file mode 100644 index 00000000..f3b3631a --- /dev/null +++ b/packages/vue/package.json @@ -0,0 +1,19 @@ +{ + "name": "@blinkdb/vue", + "version": "0.0.1", + "description": "Auxiliary package for BlinkDB & Vue", + "main": "dist/index.js", + "author": "", + "license": "MIT", + "scripts": { + "build": "tsc --outDir ./dist", + "test": "jest" + }, + "peerDependencies": { + "blinkdb": "^0.13.0", + "vue": "^3.0.0" + }, + "devDependencies": { + "@testing-library/vue": "^7.0.0" + } +} diff --git a/packages/vue/readme.md b/packages/vue/readme.md new file mode 100644 index 00000000..7c9151f5 --- /dev/null +++ b/packages/vue/readme.md @@ -0,0 +1,35 @@ +

+ BlinkDB Logo +

+ +

+ BlinkDB is a in-memory JS database optimized for large scale storage + on the frontend. +

+ +
+ +```vue + + +... +``` + +# `@blinkdb/vue` + +This package contains auxiliary methods for smoothly integrating BlinkDB into [Vue](https://vuejs.org/). + +## Getting started + +- Read the docs at https://blinkdb.io/docs/vue. +- Check out the API reference at https://blinkdb.io/docs/reference/vue. diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts new file mode 100644 index 00000000..7a543096 --- /dev/null +++ b/packages/vue/src/index.ts @@ -0,0 +1,4 @@ +export * from "./types"; +export * from "./watchFirst"; +export * from "./watchMany"; +export * from "./watchOne"; diff --git a/packages/vue/src/testutils.ts b/packages/vue/src/testutils.ts new file mode 100644 index 00000000..4fd048e3 --- /dev/null +++ b/packages/vue/src/testutils.ts @@ -0,0 +1,26 @@ +import { createApp } from "vue"; + +export interface User { + id: string; + name: string; +} + +export interface Post { + id: string; + name: string; +} + +export function withSetup(composable: () => T) { + let result: T; + const app = createApp({ + setup() { + result = composable(); + // suppress missing template warning + return () => {}; + }, + }); + app.mount(document.createElement("div")); + // return the result and the app instance + // for testing provide / unmount + return [result!, app] as const; +} diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts new file mode 100644 index 00000000..e1076292 --- /dev/null +++ b/packages/vue/src/types.ts @@ -0,0 +1,22 @@ +/** + * Result returned from a call to `watchMany()`. + */ +export type QueryResult = LoadingQueryResult | DoneQueryResult | ErrorQueryResult; + +interface LoadingQueryResult { + data: undefined; + error: undefined; + state: "loading"; +} + +interface DoneQueryResult { + data: T; + error: undefined; + state: "done"; +} + +interface ErrorQueryResult { + data: undefined; + error: Error; + state: "error"; +} diff --git a/packages/vue/src/watchFirst.spec.ts b/packages/vue/src/watchFirst.spec.ts new file mode 100644 index 00000000..8d158236 --- /dev/null +++ b/packages/vue/src/watchFirst.spec.ts @@ -0,0 +1,196 @@ +import { waitFor } from "@testing-library/vue"; +import { clear, createDB, createTable, insert, insertMany, Table, update } from "blinkdb"; +import { User, withSetup } from "./testutils"; +import { watchFirst } from "./watchFirst"; + +let userTable: Table; + +const users: User[] = [ + { id: "0", name: "Alice" }, + { id: "1", name: "Bob" }, + { id: "2", name: "Charlie" }, +]; + +beforeEach(() => { + const db = createDB(); + userTable = createTable(db, "users")(); +}); + +test("shows loading state on first render", async () => { + const [{ state, data }, app] = withSetup(() => watchFirst(userTable)); + + expect(state.value).toBe("loading"); + expect(data.value).toBe(undefined); + + app.unmount(); +}); + +test("shows done state on subsequent renders", async () => { + const [{ state, data }, app] = withSetup(() => watchFirst(userTable)); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(null); + + app.unmount(); +}); + +describe("without filter", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns first user", async () => { + const [{ state, data }, app] = withSetup(() => watchFirst(userTable)); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(users[0]); + + app.unmount(); + }); + + it("returns undefined if no users in table", async () => { + await clear(userTable); + const [{ state, data }, app] = withSetup(() => watchFirst(userTable)); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toBe(null); + + app.unmount(); + }); + + it("updates on changes", async () => { + const [{ state, data }, app] = withSetup(() => watchFirst(userTable)); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + const newUser: User = { id: "", name: "Delta" }; + await insert(userTable, newUser); + + await waitFor(() => { + expect(data.value).toStrictEqual(newUser); + }); + + app.unmount(); + }); +}); + +describe("with filter", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns first user matching filter", async () => { + const [{ state, data }, app] = withSetup(() => + watchFirst(userTable, { where: { name: "Bob" } }) + ); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(users[1]); + + app.unmount(); + }); + + it("returns undefined if no person matches filter", async () => { + const [{ state, data }, app] = withSetup(() => + watchFirst(userTable, { where: { name: "Bobby" } }) + ); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toBe(null); + + app.unmount(); + }); + + it("doesn't update on changes not matching the query", async () => { + const [{ state, data }, app] = withSetup(() => + watchFirst(userTable, { where: { name: "Bob" } }) + ); + + const newUser: User = { id: "", name: "Delta" }; + await insert(userTable, newUser); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(users[1]); + + app.unmount(); + }); + + it("updates on changes matching the query", async () => { + const [{ state, data }, app] = withSetup(() => + watchFirst(userTable, { where: { name: "Bob" } }) + ); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + const newUser: User = { id: "", name: "Bob" }; + await insert(userTable, newUser); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(newUser); + + app.unmount(); + }); +}); + +describe("with id", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns first user with the given id", async () => { + const [{ state, data }, app] = withSetup(() => watchFirst(userTable, "0")); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(users[0]); + + app.unmount(); + }); + + it("returns undefined if no person matches id", async () => { + const [{ state, data }, app] = withSetup(() => watchFirst(userTable, "999")); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toBe(null); + + app.unmount(); + }); + + it("updates on changes matching the query", async () => { + const [{ state, data }, app] = withSetup(() => watchFirst(userTable, "0")); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + await update(userTable, { ...users[0], name: "Alice the II." }); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual({ ...users[0], name: "Alice the II." }); + + app.unmount(); + }); +}); diff --git a/packages/vue/src/watchFirst.ts b/packages/vue/src/watchFirst.ts new file mode 100644 index 00000000..8dea5396 --- /dev/null +++ b/packages/vue/src/watchFirst.ts @@ -0,0 +1,66 @@ +import { Entity, key, PrimaryKeyOf, Query, Table } from "blinkdb"; +import { computed, ref, ToRefs } from "vue"; +import { QueryResult } from "./types"; +import { watchMany } from "./watchMany"; + +/** + * Retrieves the first entity from `table`. + * + * @example + * // Retrieve the first user + * const { data: firstUser } = watchFirst(userTable); + */ +export function watchFirst, P extends PrimaryKeyOf>( + table: Table +): ToRefs>; + +/** + * Retrieves the first entity from `table` matching the given `filter`. + * + * @example + * // Retrieve the first user named 'Alice' + * const { data: firstUser } = watchFirst(userTable, { + * where: { + * name: "Alice" + * } + * }); + */ +export function watchFirst, P extends PrimaryKeyOf>( + table: Table, + query: Query +): ToRefs>; + +/** + * Retrieves the first entity from `table` with the given `id`. + * + * @example + * // Retrieve the 'Alice' user by their id + * const { data: firstUser } = watchFirst(userTable, 'alice-uuid'); + */ +export function watchFirst, P extends PrimaryKeyOf>( + table: Table, + id: T[P] +): ToRefs>; + +export function watchFirst, P extends PrimaryKeyOf>( + table: Table, + queryOrId?: Query | T[P] +): ToRefs> { + let result: ToRefs>; + + if (queryOrId === undefined) { + result = watchMany(table); + } else { + const query = + typeof queryOrId === "object" + ? queryOrId + : ({ where: { [key(table)]: queryOrId } } as unknown as Query); + result = watchMany(table, query); + } + + return { + state: result.state, + data: computed(() => (result.data.value ? result.data.value[0] ?? null : undefined)), + error: ref(undefined), + } as ToRefs>; +} diff --git a/packages/vue/src/watchMany.spec.ts b/packages/vue/src/watchMany.spec.ts new file mode 100644 index 00000000..caccef60 --- /dev/null +++ b/packages/vue/src/watchMany.spec.ts @@ -0,0 +1,138 @@ +import { waitFor } from "@testing-library/vue"; +import { createDB, createTable, insert, insertMany, Table } from "blinkdb"; +import { User, withSetup } from "./testutils"; +import { watchMany } from "./watchMany"; + +let userTable: Table; + +const users: User[] = [ + { id: "0", name: "Alice" }, + { id: "1", name: "Bob" }, + { id: "2", name: "Charlie" }, +]; + +beforeEach(() => { + const db = createDB(); + userTable = createTable(db, "users")(); +}); + +test("shows loading state on first render", async () => { + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); + + expect(state.value).toBe("loading"); + expect(data.value).toBe(undefined); + + app.unmount(); +}); + +test("shows done state on subsequent renders", async () => { + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual([]); + + app.unmount(); +}); + +describe("without filter", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns users", async () => { + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(users); + + app.unmount(); + }); + + it("updates on changes", async () => { + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + const newUser: User = { id: "4", name: "Delta" }; + await insert(userTable, newUser); + + await waitFor(() => { + expect(data.value).toStrictEqual([...users, newUser]); + }); + + app.unmount(); + }); +}); + +describe("with filter", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns users", async () => { + const [{ state, data }, app] = withSetup(() => { + return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); + }); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + expect(data.value).toStrictEqual( + users.filter((u) => ["Alice", "Bob"].includes(u.name)) + ); + + app.unmount(); + }); + + it("doesn't update on changes not matching the query", async () => { + const [{ state, data }, app] = withSetup(() => { + return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); + }); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + const newUser: User = { id: "4", name: "Delta" }; + await insert(userTable, newUser); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + expect(data.value).toStrictEqual( + users.filter((u) => ["Alice", "Bob"].includes(u.name)) + ); + + app.unmount(); + }); + + it("updates on changes matching the query", async () => { + const [{ state, data }, app] = withSetup(() => { + return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); + }); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + const newUser: User = { id: "4", name: "Elise" }; + await insert(userTable, newUser); + + await new Promise((res) => setTimeout(res, 100)); + + expect(data.value).toStrictEqual([ + ...users.filter((u) => ["Alice", "Bob"].includes(u.name)), + newUser, + ]); + + app.unmount(); + }); +}); diff --git a/packages/vue/src/watchMany.ts b/packages/vue/src/watchMany.ts new file mode 100644 index 00000000..848f5a3f --- /dev/null +++ b/packages/vue/src/watchMany.ts @@ -0,0 +1,63 @@ +import { Entity, PrimaryKeyOf, Query, Table, watch } from "blinkdb"; +import { computed, onBeforeMount, onBeforeUnmount, ref, ToRefs } from "vue"; +import { QueryResult } from "./types"; + +/** + * Retrieve all entities from `table`. + * + * @example + * const queryResult = watchMany(userTable); + */ +export function watchMany, P extends PrimaryKeyOf>( + table: Table +): ToRefs>; + +/** + * Retrieve all entities from `table` that match the given `filter`. + * + * @example + * // All users called 'Alice' + * const queryResult = watchMany(userTable, { + * where: { + * name: "Alice" + * } + * }); + * // All users aged 25 and up + * const queryResult = watchMany(userTable, { + * where: { + * age: { gt: 25 } + * } + * }); + */ +export function watchMany, P extends PrimaryKeyOf>( + table: Table, + query: Query +): ToRefs>; + +export function watchMany, P extends PrimaryKeyOf>( + table: Table, + query?: Query +): ToRefs> { + const result = ref(); + let dispose: (() => void) | undefined = undefined; + + onBeforeMount(async () => { + if (query) { + dispose = await watch(table, query, (items) => { + result.value = items; + }); + } else { + dispose = await watch(table, (items) => { + result.value = items; + }); + } + }); + + onBeforeUnmount(() => dispose?.()); + + return { + data: result, + state: computed(() => (result.value === undefined ? "loading" : "done")), + error: ref(undefined), + } as ToRefs>; +} diff --git a/packages/vue/src/watchOne.spec.ts b/packages/vue/src/watchOne.spec.ts new file mode 100644 index 00000000..13eef69c --- /dev/null +++ b/packages/vue/src/watchOne.spec.ts @@ -0,0 +1,185 @@ +import { waitFor } from "@testing-library/vue"; +import { createDB, createTable, insertMany, Table, update } from "blinkdb"; +import { User, withSetup } from "./testutils"; +import { watchOne } from "./watchOne"; + +let userTable: Table; + +const users: User[] = [ + { id: "0", name: "Alice" }, + { id: "1", name: "Bob" }, + { id: "2", name: "Charlie" }, +]; + +beforeEach(() => { + const db = createDB(); + userTable = createTable(db, "users")(); +}); + +test("shows loading state on first render", async () => { + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); + + expect(state.value).toBe("loading"); + expect(data.value).toBe(undefined); + + app.unmount(); +}); + +test("shows done state on subsequent renders", async () => { + await insertMany(userTable, users); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + app.unmount(); +}); + +describe("with filter", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns first user matching filter", async () => { + const [{ state, data }, app] = withSetup(() => + watchOne(userTable, { where: { name: "Bob" } }) + ); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(users[1]); + + app.unmount(); + }); + + it("returns error if no user matches filter", async () => { + const [{ state, data, error }, app] = withSetup(() => + watchOne(userTable, { where: { name: "Bobby" } }) + ); + + await waitFor(() => { + expect(state.value).toBe("error"); + }); + expect(data.value).toBe(undefined); + expect(error.value?.message).toMatch(/No item found/); + + app.unmount(); + }); + + it("returns error if more than one user matches filter", async () => { + const [{ state, data, error }, app] = withSetup(() => + watchOne(userTable, { where: { name: { in: ["Alice", "Bob"] } } }) + ); + + await waitFor(() => { + expect(state.value).toBe("error"); + }); + expect(data.value).toBe(undefined); + expect(error.value?.message).toMatch(/More than one item found/); + + app.unmount(); + }); + + it("doesn't update on changes not matching the query", async () => { + const [{ state, data }, app] = withSetup(() => + watchOne(userTable, { where: { name: "Bob" } }) + ); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + await update(userTable, { ...users[0], name: "Alice the II." }); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(users[1]); + + app.unmount(); + }); + + it("updates on changes matching the query", async () => { + const [{ state, data }, app] = withSetup(() => + watchOne(userTable, { where: { id: "1" } }) + ); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + await update(userTable, { ...users[1], name: "Bob the II." }); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual({ ...users[1], name: "Bob the II." }); + + app.unmount(); + }); +}); + +describe("with id", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns user with the given id", async () => { + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(users[0]); + + app.unmount(); + }); + + it("returns error if no user matches id", async () => { + const [{ state, data, error }, app] = withSetup(() => watchOne(userTable, "123")); + + await waitFor(() => { + expect(state.value).toBe("error"); + }); + expect(data.value).toBe(undefined); + expect(error.value?.message).toMatch(/No item found/); + + app.unmount(); + }); + + it("updates on changes not matching the id", async () => { + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + await update(userTable, { ...users[1], name: "Bob the II." }); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual(users[0]); + + app.unmount(); + }); + + it("updates on changes matching the id", async () => { + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + + await update(userTable, { ...users[0], name: "Alice the II." }); + + await waitFor(() => { + expect(state.value).toBe("done"); + }); + expect(data.value).toStrictEqual({ ...users[0], name: "Alice the II." }); + + app.unmount(); + }); +}); diff --git a/packages/vue/src/watchOne.ts b/packages/vue/src/watchOne.ts new file mode 100644 index 00000000..3a9a60c9 --- /dev/null +++ b/packages/vue/src/watchOne.ts @@ -0,0 +1,80 @@ +import { + Entity, + ItemNotFoundError, + key, + MoreThanOneItemFoundError, + PrimaryKeyOf, + Query, + Table, +} from "blinkdb"; +import { computed, ToRefs } from "vue"; +import { QueryResult } from "./types"; +import { watchMany } from "./watchMany"; + +/** + * Retrieves the entity from `table` matching the given `filter`. + * + * @throws if no item or more than one item matches the filter. + * + * @example + * // Retrieve the only user named 'Alice' + * const queryResult = watchOne(userTable, { + * where: { + * name: "Alice" + * } + * }); + */ +export function watchOne, P extends PrimaryKeyOf>( + table: Table, + query: Query +): ToRefs>; + +/** + * Retrieves an entity from `table` with the given `id`. + * + * @throws if no item matches the given id. + * + * @example + * // Retrieve the 'Alice' user by their id + * const queryResult = watchOne(userTable, 'alice-uuid'); + */ +export function watchOne, P extends PrimaryKeyOf>( + table: Table, + id: T[P] +): ToRefs>; + +export function watchOne, P extends PrimaryKeyOf>( + table: Table, + queryOrId: Query | T[P] +): ToRefs> { + let result: ToRefs>; + + if (queryOrId === undefined) { + result = watchMany(table); + } else { + const query = + typeof queryOrId === "object" + ? queryOrId + : ({ where: { [key(table)]: queryOrId } } as unknown as Query); + result = watchMany(table, query); + } + + const error = computed(() => { + if (result.state.value === "done") { + if (result.data.value!.length === 0) { + return new ItemNotFoundError(queryOrId); + } + if (result.data.value!.length > 1) { + return new MoreThanOneItemFoundError(queryOrId); + } + } + }); + + return { + error, + state: computed(() => (error.value !== undefined ? "error" : result.state.value)), + data: computed(() => + error.value === undefined && result.data.value ? result.data.value[0] : undefined + ), + } as ToRefs>; +} diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json new file mode 100644 index 00000000..ad768e0e --- /dev/null +++ b/packages/vue/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declaration": true + }, + "include": ["**/src/*.ts"] +} \ No newline at end of file pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy