From cc869517d8498dbd37f3c25a177c501a79ff1a3d Mon Sep 17 00:00:00 2001 From: froehlichA Date: Tue, 11 Jul 2023 23:31:18 +0200 Subject: [PATCH 01/15] init vue package --- package-lock.json | 748 +++++++++++++++++++++++++++++++++++- packages/vue/jest.config.ts | 14 + packages/vue/package.json | 19 + packages/vue/readme.md | 23 ++ packages/vue/tsconfig.json | 8 + 5 files changed, 800 insertions(+), 12 deletions(-) create mode 100644 packages/vue/jest.config.ts create mode 100644 packages/vue/package.json create mode 100644 packages/vue/readme.md create mode 100644 packages/vue/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 28f1783..a78e219 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": "*" + } + }, "@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/packages/vue/jest.config.ts b/packages/vue/jest.config.ts new file mode 100644 index 0000000..ecd7732 --- /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 0000000..f3b3631 --- /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 0000000..b71c1fd --- /dev/null +++ b/packages/vue/readme.md @@ -0,0 +1,23 @@ +

+ BlinkDB Logo +

+ +

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

+ +
+ +```tsx +// Showcase here +``` + +# `@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/tsconfig.json b/packages/vue/tsconfig.json new file mode 100644 index 0000000..ad768e0 --- /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 From ae060f7cf18230f2c72f95c88e807f3702c2942e Mon Sep 17 00:00:00 2001 From: froehlichA Date: Wed, 12 Jul 2023 23:46:30 +0200 Subject: [PATCH 02/15] implement watchMany --- packages/docs/.astro/types.d.ts | 7 ++ packages/vue/src/index.ts | 3 + packages/vue/src/testUtils.ts | 26 ++++++ packages/vue/src/types.ts | 25 ++++++ packages/vue/src/watchMany.spec.ts | 138 +++++++++++++++++++++++++++++ packages/vue/src/watchMany.ts | 69 +++++++++++++++ 6 files changed, 268 insertions(+) create mode 100644 packages/vue/src/index.ts create mode 100644 packages/vue/src/testUtils.ts create mode 100644 packages/vue/src/types.ts create mode 100644 packages/vue/src/watchMany.spec.ts create mode 100644 packages/vue/src/watchMany.ts diff --git a/packages/docs/.astro/types.d.ts b/packages/docs/.astro/types.d.ts index 90cebef..7fd3bd3 100644 --- a/packages/docs/.astro/types.d.ts +++ b/packages/docs/.astro/types.d.ts @@ -236,6 +236,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"; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts new file mode 100644 index 0000000..240b62a --- /dev/null +++ b/packages/vue/src/index.ts @@ -0,0 +1,3 @@ +export * from "./types"; +export * from "./watchFirst"; +export * from "./watchMany"; diff --git a/packages/vue/src/testUtils.ts b/packages/vue/src/testUtils.ts new file mode 100644 index 0000000..4fd048e --- /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 0000000..cc92aa5 --- /dev/null +++ b/packages/vue/src/types.ts @@ -0,0 +1,25 @@ +/** + * 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/watchMany.spec.ts b/packages/vue/src/watchMany.spec.ts new file mode 100644 index 0000000..af27ebf --- /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 [result, app] = withSetup(() => watchMany(userTable)); + + expect(result.value.state).toBe("loading"); + expect(result.value.data).toBe(undefined); + + app.unmount(); +}); + +test("shows done state on subsequent renders", async () => { + const [result, app] = withSetup(() => watchMany(userTable)); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual([]); + + app.unmount(); +}); + +describe("without filter", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns users", async () => { + const [result, app] = withSetup(() => watchMany(userTable)); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(users); + + app.unmount(); + }); + + it("updates on changes", async () => { + const [result, app] = withSetup(() => watchMany(userTable)); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + const newUser: User = { id: "4", name: "Delta" }; + await insert(userTable, newUser); + + await waitFor(() => { + expect(result.value.data).toStrictEqual([...users, newUser]); + }); + + app.unmount(); + }); +}); + +describe("with filter", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns users", async () => { + const [result, app] = withSetup(() => { + return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); + }); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + expect(result.value.data).toStrictEqual( + users.filter((u) => ["Alice", "Bob"].includes(u.name)) + ); + + app.unmount(); + }); + + it("doesn't update on changes not matching the query", async () => { + const [result, app] = withSetup(() => { + return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); + }); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + const newUser: User = { id: "4", name: "Delta" }; + await insert(userTable, newUser); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + expect(result.value.data).toStrictEqual( + users.filter((u) => ["Alice", "Bob"].includes(u.name)) + ); + + app.unmount(); + }); + + it("updates on changes matching the query", async () => { + const [result, app] = withSetup(() => { + return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); + }); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + const newUser: User = { id: "4", name: "Elise" }; + await insert(userTable, newUser); + + await new Promise((res) => setTimeout(res, 100)); + + expect(result.value.data).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 0000000..61f5105 --- /dev/null +++ b/packages/vue/src/watchMany.ts @@ -0,0 +1,69 @@ +import { Entity, PrimaryKeyOf, Query, Table, watch } from "blinkdb"; +import { computed, ComputedRef, onBeforeMount, onBeforeUnmount, ref } from "vue"; +import { QueryResult } from "./types"; + +/** + * Retrieve all entities from `table`. + * + * @example + * const queryResult = watchMany(userTable); + */ +export function watchMany, P extends PrimaryKeyOf>( + table: Table +): ComputedRef>; + +/** + * 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 +): ComputedRef>; + +export function watchMany, P extends PrimaryKeyOf>( + table: Table, + query?: Query +): ComputedRef> { + const state = ref(); + let dispose: (() => void) | undefined = undefined; + + onBeforeMount(async () => { + if (query) { + dispose = await watch(table, query, (items) => { + state.value = items; + console.log("UPDATE!", items); + }); + } else { + dispose = await watch(table, (items) => { + state.value = items; + console.log("UPDATE!", items); + }); + } + }); + + onBeforeUnmount(() => dispose?.()); + + return computed( + () => + ({ + data: state.value, + state: state.value === undefined ? "loading" : "done", + error: undefined, + } as QueryResult), + { onTrigger: console.log } + ); +} From 7004b54aafab46c4ac07b63c7f607999529c848f Mon Sep 17 00:00:00 2001 From: froehlichA Date: Thu, 13 Jul 2023 00:51:55 +0200 Subject: [PATCH 03/15] implement watchOne --- packages/vue/src/index.ts | 1 + packages/vue/src/watchOne.spec.ts | 183 ++++++++++++++++++++++++++++++ packages/vue/src/watchOne.ts | 84 ++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 packages/vue/src/watchOne.spec.ts create mode 100644 packages/vue/src/watchOne.ts diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 240b62a..7a54309 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,3 +1,4 @@ export * from "./types"; export * from "./watchFirst"; export * from "./watchMany"; +export * from "./watchOne"; diff --git a/packages/vue/src/watchOne.spec.ts b/packages/vue/src/watchOne.spec.ts new file mode 100644 index 0000000..714903a --- /dev/null +++ b/packages/vue/src/watchOne.spec.ts @@ -0,0 +1,183 @@ +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 [result, app] = withSetup(() => watchOne(userTable, "0")); + + expect(result.value.state).toBe("loading"); + expect(result.value.data).toBe(undefined); + + app.unmount(); +}); + +test("shows done state on subsequent renders", async () => { + await insertMany(userTable, users); + const [result, app] = withSetup(() => watchOne(userTable, "0")); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + app.unmount(); +}); + +describe("with filter", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns first user matching filter", async () => { + const [result, app] = withSetup(() => + watchOne(userTable, { where: { name: "Bob" } }) + ); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(users[1]); + + app.unmount(); + }); + + it("returns error if no user matches filter", async () => { + const [result, app] = withSetup(() => + watchOne(userTable, { where: { name: "Bobby" } }) + ); + + await waitFor(() => { + expect(result.value.state).toBe("error"); + }); + expect(result.value.data).toBe(undefined); + expect(result.value.error?.message).toMatch(/No item found/); + + app.unmount(); + }); + + it("returns error if more than one user matches filter", async () => { + const [result, app] = withSetup(() => + watchOne(userTable, { where: { name: { in: ["Alice", "Bob"] } } }) + ); + + await waitFor(() => { + expect(result.value.state).toBe("error"); + }); + expect(result.value.data).toBe(undefined); + expect(result.value.error?.message).toMatch(/More than one item found/); + + app.unmount(); + }); + + it("doesn't update on changes not matching the query", async () => { + const [result, app] = withSetup(() => + watchOne(userTable, { where: { name: "Bob" } }) + ); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + await update(userTable, { ...users[0], name: "Alice the II." }); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(users[1]); + + app.unmount(); + }); + + it("updates on changes matching the query", async () => { + const [result, app] = withSetup(() => watchOne(userTable, { where: { id: "1" } })); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + await update(userTable, { ...users[1], name: "Bob the II." }); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).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 [result, app] = withSetup(() => watchOne(userTable, "0")); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(users[0]); + + app.unmount(); + }); + + it("returns error if no user matches id", async () => { + const [result, app] = withSetup(() => watchOne(userTable, "123")); + + await waitFor(() => { + expect(result.value.state).toBe("error"); + }); + expect(result.value.data).toBe(undefined); + expect(result.value.error?.message).toMatch(/No item found/); + + app.unmount(); + }); + + it("updates on changes not matching the id", async () => { + const [result, app] = withSetup(() => watchOne(userTable, "0")); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + await update(userTable, { ...users[1], name: "Bob the II." }); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(users[0]); + + app.unmount(); + }); + + it("updates on changes matching the id", async () => { + const [result, app] = withSetup(() => watchOne(userTable, "0")); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + await update(userTable, { ...users[0], name: "Alice the II." }); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).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 0000000..4c4953a --- /dev/null +++ b/packages/vue/src/watchOne.ts @@ -0,0 +1,84 @@ +import { + Entity, + ItemNotFoundError, + key, + MoreThanOneItemFoundError, + PrimaryKeyOf, + Query, + Table, +} from "blinkdb"; +import { computed, ComputedRef } 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 +): ComputedRef>; + +/** + * 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] +): ComputedRef>; + +export function watchOne, P extends PrimaryKeyOf>( + table: Table, + queryOrId: Query | T[P] +): ComputedRef> { + const primaryKeyProperty = key(table); + let result: ComputedRef>; + if (queryOrId === undefined) { + result = watchMany(table); + } else { + const query = + typeof queryOrId === "object" + ? queryOrId + : ({ where: { [primaryKeyProperty]: queryOrId } } as unknown as Query); + result = watchMany(table, query); + } + + return computed((): QueryResult => { + if (result.value.state === "done") { + if (result.value.data.length === 0) { + return { + state: "error", + data: undefined, + error: new ItemNotFoundError(queryOrId), + }; + } else if (result.value.data.length > 1) { + return { + state: "error", + data: undefined, + error: new MoreThanOneItemFoundError(queryOrId), + }; + } + } + + return { + ...result.value, + data: result.value.data ? result.value.data[0] : undefined, + } as QueryResult; + }); +} From 4219c23c62b415e8277c3b094c983c0ef3ce48d8 Mon Sep 17 00:00:00 2001 From: froehlichA Date: Thu, 13 Jul 2023 01:03:09 +0200 Subject: [PATCH 04/15] implement watchFirst --- packages/vue/src/watchFirst.spec.ts | 196 ++++++++++++++++++++++++++++ packages/vue/src/watchFirst.ts | 68 ++++++++++ 2 files changed, 264 insertions(+) create mode 100644 packages/vue/src/watchFirst.spec.ts create mode 100644 packages/vue/src/watchFirst.ts diff --git a/packages/vue/src/watchFirst.spec.ts b/packages/vue/src/watchFirst.spec.ts new file mode 100644 index 0000000..a3d3b09 --- /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 [result, app] = withSetup(() => watchFirst(userTable)); + + expect(result.value.state).toBe("loading"); + expect(result.value.data).toBe(undefined); + + app.unmount(); +}); + +test("shows done state on subsequent renders", async () => { + const [result, app] = withSetup(() => watchFirst(userTable)); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(null); + + app.unmount(); +}); + +describe("without filter", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns first user", async () => { + const [result, app] = withSetup(() => watchFirst(userTable)); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(users[0]); + + app.unmount(); + }); + + it("returns undefined if no users in table", async () => { + await clear(userTable); + const [result, app] = withSetup(() => watchFirst(userTable)); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toBe(null); + + app.unmount(); + }); + + it("updates on changes", async () => { + const [result, app] = withSetup(() => watchFirst(userTable)); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + const newUser: User = { id: "", name: "Delta" }; + await insert(userTable, newUser); + + await waitFor(() => { + expect(result.value.data).toStrictEqual(newUser); + }); + + app.unmount(); + }); +}); + +describe("with filter", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns first user matching filter", async () => { + const [result, app] = withSetup(() => + watchFirst(userTable, { where: { name: "Bob" } }) + ); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(users[1]); + + app.unmount(); + }); + + it("returns undefined if no person matches filter", async () => { + const [result, app] = withSetup(() => + watchFirst(userTable, { where: { name: "Bobby" } }) + ); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toBe(null); + + app.unmount(); + }); + + it("doesn't update on changes not matching the query", async () => { + const [result, app] = withSetup(() => + watchFirst(userTable, { where: { name: "Bob" } }) + ); + + const newUser: User = { id: "", name: "Delta" }; + await insert(userTable, newUser); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(users[1]); + + app.unmount(); + }); + + it("updates on changes matching the query", async () => { + const [result, app] = withSetup(() => + watchFirst(userTable, { where: { name: "Bob" } }) + ); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + const newUser: User = { id: "", name: "Bob" }; + await insert(userTable, newUser); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(newUser); + + app.unmount(); + }); +}); + +describe("with id", () => { + beforeEach(async () => { + await insertMany(userTable, users); + }); + + it("returns first user with the given id", async () => { + const [result, app] = withSetup(() => watchFirst(userTable, "0")); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toStrictEqual(users[0]); + + app.unmount(); + }); + + it("returns undefined if no person matches id", async () => { + const [result, app] = withSetup(() => watchFirst(userTable, "999")); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).toBe(null); + + app.unmount(); + }); + + it("updates on changes matching the query", async () => { + const [result, app] = withSetup(() => watchFirst(userTable, "0")); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + + await update(userTable, { ...users[0], name: "Alice the II." }); + + await waitFor(() => { + expect(result.value.state).toBe("done"); + }); + expect(result.value.data).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 0000000..9b880e8 --- /dev/null +++ b/packages/vue/src/watchFirst.ts @@ -0,0 +1,68 @@ +import { Entity, key, PrimaryKeyOf, Query, Table } from "blinkdb"; +import { computed, ComputedRef } from "vue"; +import { QueryResult } from "./types"; +import { watchMany } from "./watchMany"; + +/** + * Retrieves the first entity from `table`. + * + * @example + * // Retrieve the first user + * const queryResult = watchFirst(userTable); + */ +export function watchFirst, P extends PrimaryKeyOf>( + table: Table +): ComputedRef>; + +/** + * Retrieves the first entity from `table` matching the given `filter`. + * + * @example + * // Retrieve the first user named 'Alice' + * const queryResult = watchFirst(userTable, { + * where: { + * name: "Alice" + * } + * }); + */ +export function watchFirst, P extends PrimaryKeyOf>( + table: Table, + query: Query +): ComputedRef>; + +/** + * Retrieves the first entity from `table` with the given `id`. + * + * @example + * // Retrieve the 'Alice' user by their id + * const queryResult = watchFirst(userTable, 'alice-uuid'); + */ +export function watchFirst, P extends PrimaryKeyOf>( + table: Table, + id: T[P] +): ComputedRef>; + +export function watchFirst, P extends PrimaryKeyOf>( + table: Table, + queryOrId?: Query | T[P] +): ComputedRef> { + const primaryKeyProperty = key(table); + let result: ComputedRef>; + if (queryOrId === undefined) { + result = watchMany(table); + } else { + const query = + typeof queryOrId === "object" + ? queryOrId + : ({ where: { [primaryKeyProperty]: queryOrId } } as unknown as Query); + result = watchMany(table, query); + } + + return computed(() => { + return { + ...result.value, + data: result.value.data ? result.value.data[0] ?? null : undefined, + error: undefined, + } as QueryResult; + }); +} From 6bd5dd5c08f3f4018d14a6089c5db8f85821dcf6 Mon Sep 17 00:00:00 2001 From: froehlichA Date: Thu, 13 Jul 2023 01:13:13 +0200 Subject: [PATCH 05/15] remove console.log --- packages/vue/src/watchMany.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/vue/src/watchMany.ts b/packages/vue/src/watchMany.ts index 61f5105..9fd506f 100644 --- a/packages/vue/src/watchMany.ts +++ b/packages/vue/src/watchMany.ts @@ -45,12 +45,10 @@ export function watchMany, P extends PrimaryKeyOf>( if (query) { dispose = await watch(table, query, (items) => { state.value = items; - console.log("UPDATE!", items); }); } else { dispose = await watch(table, (items) => { state.value = items; - console.log("UPDATE!", items); }); } }); From 50375716cd63b7c8b2c1182834160cedf07d35e0 Mon Sep 17 00:00:00 2001 From: froehlichA Date: Thu, 13 Jul 2023 01:14:53 +0200 Subject: [PATCH 06/15] fix build errors --- packages/vue/src/{testUtils.ts => testutils.ts} | 0 packages/vue/src/watchMany.spec.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/vue/src/{testUtils.ts => testutils.ts} (100%) diff --git a/packages/vue/src/testUtils.ts b/packages/vue/src/testutils.ts similarity index 100% rename from packages/vue/src/testUtils.ts rename to packages/vue/src/testutils.ts diff --git a/packages/vue/src/watchMany.spec.ts b/packages/vue/src/watchMany.spec.ts index af27ebf..74e7834 100644 --- a/packages/vue/src/watchMany.spec.ts +++ b/packages/vue/src/watchMany.spec.ts @@ -1,6 +1,6 @@ import { waitFor } from "@testing-library/vue"; import { createDB, createTable, insert, insertMany, Table } from "blinkdb"; -import { User, withSetup } from "./testUtils"; +import { User, withSetup } from "./testutils"; import { watchMany } from "./watchMany"; let userTable: Table; From 4daa39b8b273ed18d007eaa7257a1efa06a5c9cb Mon Sep 17 00:00:00 2001 From: froehlichA Date: Sun, 16 Jul 2023 22:32:37 +0200 Subject: [PATCH 07/15] add vue example to readme --- packages/vue/readme.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/vue/readme.md b/packages/vue/readme.md index b71c1fd..7c9151f 100644 --- a/packages/vue/readme.md +++ b/packages/vue/readme.md @@ -9,8 +9,20 @@
-```tsx -// Showcase here +```vue + + +... ``` # `@blinkdb/vue` From 7511f6a52d35d61e54c3855aee6608c37eed891e Mon Sep 17 00:00:00 2001 From: froehlichA Date: Sun, 16 Jul 2023 22:34:33 +0200 Subject: [PATCH 08/15] fix react example --- packages/react/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/readme.md b/packages/react/readme.md index 0b533c7..50e464b 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 }, }, }); + ... } ``` From e6c5f4835ef753f240ab1efdfcca34ad93fcd255 Mon Sep 17 00:00:00 2001 From: froehlichA Date: Sun, 16 Jul 2023 22:36:20 +0200 Subject: [PATCH 09/15] remove incorrect await in react docs --- .../docs/src/content/reference/useFirst.mdx | 10 +++--- .../docs/src/content/reference/useMany.mdx | 8 ++--- packages/react/src/retrieval/useFirst.ts | 19 +++++----- packages/react/src/retrieval/useOne.ts | 35 ++++++++++++------- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/packages/docs/src/content/reference/useFirst.mdx b/packages/docs/src/content/reference/useFirst.mdx index 3c3541e..36d2712 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" } @@ -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 35922e2..9e64cf7 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" } @@ -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/react/src/retrieval/useFirst.ts b/packages/react/src/retrieval/useFirst.ts index e54bd0c..4bc81b1 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 4a92785..43662ca 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 +} From 02391879ffaaa0021dda3e572c6f8134be0ec4b0 Mon Sep 17 00:00:00 2001 From: froehlichA Date: Fri, 28 Jul 2023 23:11:27 +0200 Subject: [PATCH 10/15] adapt API of watchFirst to use individual refs --- packages/vue/src/watchFirst.spec.ts | 76 ++++++++++++++--------------- packages/vue/src/watchFirst.ts | 28 +++++------ 2 files changed, 51 insertions(+), 53 deletions(-) diff --git a/packages/vue/src/watchFirst.spec.ts b/packages/vue/src/watchFirst.spec.ts index a3d3b09..8d15823 100644 --- a/packages/vue/src/watchFirst.spec.ts +++ b/packages/vue/src/watchFirst.spec.ts @@ -17,21 +17,21 @@ beforeEach(() => { }); test("shows loading state on first render", async () => { - const [result, app] = withSetup(() => watchFirst(userTable)); + const [{ state, data }, app] = withSetup(() => watchFirst(userTable)); - expect(result.value.state).toBe("loading"); - expect(result.value.data).toBe(undefined); + expect(state.value).toBe("loading"); + expect(data.value).toBe(undefined); app.unmount(); }); test("shows done state on subsequent renders", async () => { - const [result, app] = withSetup(() => watchFirst(userTable)); + const [{ state, data }, app] = withSetup(() => watchFirst(userTable)); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(null); + expect(data.value).toStrictEqual(null); app.unmount(); }); @@ -42,40 +42,40 @@ describe("without filter", () => { }); it("returns first user", async () => { - const [result, app] = withSetup(() => watchFirst(userTable)); + const [{ state, data }, app] = withSetup(() => watchFirst(userTable)); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[0]); + expect(data.value).toStrictEqual(users[0]); app.unmount(); }); it("returns undefined if no users in table", async () => { await clear(userTable); - const [result, app] = withSetup(() => watchFirst(userTable)); + const [{ state, data }, app] = withSetup(() => watchFirst(userTable)); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toBe(null); + expect(data.value).toBe(null); app.unmount(); }); it("updates on changes", async () => { - const [result, app] = withSetup(() => watchFirst(userTable)); + const [{ state, data }, app] = withSetup(() => watchFirst(userTable)); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); const newUser: User = { id: "", name: "Delta" }; await insert(userTable, newUser); await waitFor(() => { - expect(result.value.data).toStrictEqual(newUser); + expect(data.value).toStrictEqual(newUser); }); app.unmount(); @@ -88,33 +88,33 @@ describe("with filter", () => { }); it("returns first user matching filter", async () => { - const [result, app] = withSetup(() => + const [{ state, data }, app] = withSetup(() => watchFirst(userTable, { where: { name: "Bob" } }) ); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[1]); + expect(data.value).toStrictEqual(users[1]); app.unmount(); }); it("returns undefined if no person matches filter", async () => { - const [result, app] = withSetup(() => + const [{ state, data }, app] = withSetup(() => watchFirst(userTable, { where: { name: "Bobby" } }) ); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toBe(null); + expect(data.value).toBe(null); app.unmount(); }); it("doesn't update on changes not matching the query", async () => { - const [result, app] = withSetup(() => + const [{ state, data }, app] = withSetup(() => watchFirst(userTable, { where: { name: "Bob" } }) ); @@ -122,29 +122,29 @@ describe("with filter", () => { await insert(userTable, newUser); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[1]); + expect(data.value).toStrictEqual(users[1]); app.unmount(); }); it("updates on changes matching the query", async () => { - const [result, app] = withSetup(() => + const [{ state, data }, app] = withSetup(() => watchFirst(userTable, { where: { name: "Bob" } }) ); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); const newUser: User = { id: "", name: "Bob" }; await insert(userTable, newUser); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(newUser); + expect(data.value).toStrictEqual(newUser); app.unmount(); }); @@ -156,40 +156,40 @@ describe("with id", () => { }); it("returns first user with the given id", async () => { - const [result, app] = withSetup(() => watchFirst(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchFirst(userTable, "0")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[0]); + expect(data.value).toStrictEqual(users[0]); app.unmount(); }); it("returns undefined if no person matches id", async () => { - const [result, app] = withSetup(() => watchFirst(userTable, "999")); + const [{ state, data }, app] = withSetup(() => watchFirst(userTable, "999")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toBe(null); + expect(data.value).toBe(null); app.unmount(); }); it("updates on changes matching the query", async () => { - const [result, app] = withSetup(() => watchFirst(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchFirst(userTable, "0")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); await update(userTable, { ...users[0], name: "Alice the II." }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual({ ...users[0], name: "Alice the II." }); + 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 index 9b880e8..f868b3f 100644 --- a/packages/vue/src/watchFirst.ts +++ b/packages/vue/src/watchFirst.ts @@ -1,5 +1,5 @@ import { Entity, key, PrimaryKeyOf, Query, Table } from "blinkdb"; -import { computed, ComputedRef } from "vue"; +import { computed, ComputedRef, ref, ToRefs } from "vue"; import { QueryResult } from "./types"; import { watchMany } from "./watchMany"; @@ -8,18 +8,18 @@ import { watchMany } from "./watchMany"; * * @example * // Retrieve the first user - * const queryResult = watchFirst(userTable); + * const { data: firstUser } = watchFirst(userTable); */ export function watchFirst, P extends PrimaryKeyOf>( table: Table -): ComputedRef>; +): ToRefs>; /** * Retrieves the first entity from `table` matching the given `filter`. * * @example * // Retrieve the first user named 'Alice' - * const queryResult = watchFirst(userTable, { + * const { data: firstUser } = watchFirst(userTable, { * where: { * name: "Alice" * } @@ -28,24 +28,24 @@ export function watchFirst, P extends PrimaryKeyOf>( export function watchFirst, P extends PrimaryKeyOf>( table: Table, query: Query -): ComputedRef>; +): ToRefs>; /** * Retrieves the first entity from `table` with the given `id`. * * @example * // Retrieve the 'Alice' user by their id - * const queryResult = watchFirst(userTable, 'alice-uuid'); + * const { data: firstUser } = watchFirst(userTable, 'alice-uuid'); */ export function watchFirst, P extends PrimaryKeyOf>( table: Table, id: T[P] -): ComputedRef>; +): ToRefs>; export function watchFirst, P extends PrimaryKeyOf>( table: Table, queryOrId?: Query | T[P] -): ComputedRef> { +): ToRefs> { const primaryKeyProperty = key(table); let result: ComputedRef>; if (queryOrId === undefined) { @@ -58,11 +58,9 @@ export function watchFirst, P extends PrimaryKeyOf>( result = watchMany(table, query); } - return computed(() => { - return { - ...result.value, - data: result.value.data ? result.value.data[0] ?? null : undefined, - error: undefined, - } as QueryResult; - }); + return { + state: computed(() => result.value.state), + data: computed(() => (result.value.data ? result.value.data[0] ?? null : undefined)), + error: ref(undefined), + } as ToRefs>; } From dc1ec551983c11a7365d0fcb9fb7e254b8a0922c Mon Sep 17 00:00:00 2001 From: froehlichA Date: Tue, 10 Oct 2023 19:28:47 +0200 Subject: [PATCH 11/15] improve vue query types --- packages/vue/src/types.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts index cc92aa5..e107629 100644 --- a/packages/vue/src/types.ts +++ b/packages/vue/src/types.ts @@ -1,12 +1,9 @@ /** * Result returned from a call to `watchMany()`. */ -export type QueryResult = - | LoadingQueryResult - | DoneQueryResult - | ErrorQueryResult; +export type QueryResult = LoadingQueryResult | DoneQueryResult | ErrorQueryResult; -interface LoadingQueryResult { +interface LoadingQueryResult { data: undefined; error: undefined; state: "loading"; @@ -18,7 +15,7 @@ interface DoneQueryResult { state: "done"; } -interface ErrorQueryResult { +interface ErrorQueryResult { data: undefined; error: Error; state: "error"; From 7f35459d3c738a2e2e7b905963c60f3fd7e7c823 Mon Sep 17 00:00:00 2001 From: froehlichA Date: Tue, 10 Oct 2023 19:29:16 +0200 Subject: [PATCH 12/15] adapt API of watchMany & watchOne to use individual refs --- packages/vue/src/watchFirst.ts | 12 ++--- packages/vue/src/watchMany.spec.ts | 44 ++++++++-------- packages/vue/src/watchMany.ts | 28 +++++------ packages/vue/src/watchOne.spec.ts | 80 +++++++++++++++--------------- packages/vue/src/watchOne.ts | 48 ++++++++---------- 5 files changed, 103 insertions(+), 109 deletions(-) diff --git a/packages/vue/src/watchFirst.ts b/packages/vue/src/watchFirst.ts index f868b3f..8dea539 100644 --- a/packages/vue/src/watchFirst.ts +++ b/packages/vue/src/watchFirst.ts @@ -1,5 +1,5 @@ import { Entity, key, PrimaryKeyOf, Query, Table } from "blinkdb"; -import { computed, ComputedRef, ref, ToRefs } from "vue"; +import { computed, ref, ToRefs } from "vue"; import { QueryResult } from "./types"; import { watchMany } from "./watchMany"; @@ -46,21 +46,21 @@ export function watchFirst, P extends PrimaryKeyOf>( table: Table, queryOrId?: Query | T[P] ): ToRefs> { - const primaryKeyProperty = key(table); - let result: ComputedRef>; + let result: ToRefs>; + if (queryOrId === undefined) { result = watchMany(table); } else { const query = typeof queryOrId === "object" ? queryOrId - : ({ where: { [primaryKeyProperty]: queryOrId } } as unknown as Query); + : ({ where: { [key(table)]: queryOrId } } as unknown as Query); result = watchMany(table, query); } return { - state: computed(() => result.value.state), - data: computed(() => (result.value.data ? result.value.data[0] ?? null : undefined)), + 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 index 74e7834..caccef6 100644 --- a/packages/vue/src/watchMany.spec.ts +++ b/packages/vue/src/watchMany.spec.ts @@ -17,21 +17,21 @@ beforeEach(() => { }); test("shows loading state on first render", async () => { - const [result, app] = withSetup(() => watchMany(userTable)); + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); - expect(result.value.state).toBe("loading"); - expect(result.value.data).toBe(undefined); + expect(state.value).toBe("loading"); + expect(data.value).toBe(undefined); app.unmount(); }); test("shows done state on subsequent renders", async () => { - const [result, app] = withSetup(() => watchMany(userTable)); + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual([]); + expect(data.value).toStrictEqual([]); app.unmount(); }); @@ -42,28 +42,28 @@ describe("without filter", () => { }); it("returns users", async () => { - const [result, app] = withSetup(() => watchMany(userTable)); + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users); + expect(data.value).toStrictEqual(users); app.unmount(); }); it("updates on changes", async () => { - const [result, app] = withSetup(() => watchMany(userTable)); + const [{ state, data }, app] = withSetup(() => watchMany(userTable)); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); const newUser: User = { id: "4", name: "Delta" }; await insert(userTable, newUser); await waitFor(() => { - expect(result.value.data).toStrictEqual([...users, newUser]); + expect(data.value).toStrictEqual([...users, newUser]); }); app.unmount(); @@ -76,15 +76,15 @@ describe("with filter", () => { }); it("returns users", async () => { - const [result, app] = withSetup(() => { + const [{ state, data }, app] = withSetup(() => { return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual( + expect(data.value).toStrictEqual( users.filter((u) => ["Alice", "Bob"].includes(u.name)) ); @@ -92,22 +92,22 @@ describe("with filter", () => { }); it("doesn't update on changes not matching the query", async () => { - const [result, app] = withSetup(() => { + const [{ state, data }, app] = withSetup(() => { return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); const newUser: User = { id: "4", name: "Delta" }; await insert(userTable, newUser); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual( + expect(data.value).toStrictEqual( users.filter((u) => ["Alice", "Bob"].includes(u.name)) ); @@ -115,12 +115,12 @@ describe("with filter", () => { }); it("updates on changes matching the query", async () => { - const [result, app] = withSetup(() => { + const [{ state, data }, app] = withSetup(() => { return watchMany(userTable, { where: { name: { in: ["Alice", "Bob", "Elise"] } } }); }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); const newUser: User = { id: "4", name: "Elise" }; @@ -128,7 +128,7 @@ describe("with filter", () => { await new Promise((res) => setTimeout(res, 100)); - expect(result.value.data).toStrictEqual([ + expect(data.value).toStrictEqual([ ...users.filter((u) => ["Alice", "Bob"].includes(u.name)), newUser, ]); diff --git a/packages/vue/src/watchMany.ts b/packages/vue/src/watchMany.ts index 9fd506f..848f5a3 100644 --- a/packages/vue/src/watchMany.ts +++ b/packages/vue/src/watchMany.ts @@ -1,5 +1,5 @@ import { Entity, PrimaryKeyOf, Query, Table, watch } from "blinkdb"; -import { computed, ComputedRef, onBeforeMount, onBeforeUnmount, ref } from "vue"; +import { computed, onBeforeMount, onBeforeUnmount, ref, ToRefs } from "vue"; import { QueryResult } from "./types"; /** @@ -10,7 +10,7 @@ import { QueryResult } from "./types"; */ export function watchMany, P extends PrimaryKeyOf>( table: Table -): ComputedRef>; +): ToRefs>; /** * Retrieve all entities from `table` that match the given `filter`. @@ -32,36 +32,32 @@ export function watchMany, P extends PrimaryKeyOf>( export function watchMany, P extends PrimaryKeyOf>( table: Table, query: Query -): ComputedRef>; +): ToRefs>; export function watchMany, P extends PrimaryKeyOf>( table: Table, query?: Query -): ComputedRef> { - const state = ref(); +): ToRefs> { + const result = ref(); let dispose: (() => void) | undefined = undefined; onBeforeMount(async () => { if (query) { dispose = await watch(table, query, (items) => { - state.value = items; + result.value = items; }); } else { dispose = await watch(table, (items) => { - state.value = items; + result.value = items; }); } }); onBeforeUnmount(() => dispose?.()); - return computed( - () => - ({ - data: state.value, - state: state.value === undefined ? "loading" : "done", - error: undefined, - } as QueryResult), - { onTrigger: console.log } - ); + 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 index 714903a..13eef69 100644 --- a/packages/vue/src/watchOne.spec.ts +++ b/packages/vue/src/watchOne.spec.ts @@ -17,20 +17,20 @@ beforeEach(() => { }); test("shows loading state on first render", async () => { - const [result, app] = withSetup(() => watchOne(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); - expect(result.value.state).toBe("loading"); - expect(result.value.data).toBe(undefined); + 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 [result, app] = withSetup(() => watchOne(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); app.unmount(); @@ -42,78 +42,80 @@ describe("with filter", () => { }); it("returns first user matching filter", async () => { - const [result, app] = withSetup(() => + const [{ state, data }, app] = withSetup(() => watchOne(userTable, { where: { name: "Bob" } }) ); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[1]); + expect(data.value).toStrictEqual(users[1]); app.unmount(); }); it("returns error if no user matches filter", async () => { - const [result, app] = withSetup(() => + const [{ state, data, error }, app] = withSetup(() => watchOne(userTable, { where: { name: "Bobby" } }) ); await waitFor(() => { - expect(result.value.state).toBe("error"); + expect(state.value).toBe("error"); }); - expect(result.value.data).toBe(undefined); - expect(result.value.error?.message).toMatch(/No item found/); + 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 [result, app] = withSetup(() => + const [{ state, data, error }, app] = withSetup(() => watchOne(userTable, { where: { name: { in: ["Alice", "Bob"] } } }) ); await waitFor(() => { - expect(result.value.state).toBe("error"); + expect(state.value).toBe("error"); }); - expect(result.value.data).toBe(undefined); - expect(result.value.error?.message).toMatch(/More than one item found/); + 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 [result, app] = withSetup(() => + const [{ state, data }, app] = withSetup(() => watchOne(userTable, { where: { name: "Bob" } }) ); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); await update(userTable, { ...users[0], name: "Alice the II." }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[1]); + expect(data.value).toStrictEqual(users[1]); app.unmount(); }); it("updates on changes matching the query", async () => { - const [result, app] = withSetup(() => watchOne(userTable, { where: { id: "1" } })); + const [{ state, data }, app] = withSetup(() => + watchOne(userTable, { where: { id: "1" } }) + ); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); await update(userTable, { ...users[1], name: "Bob the II." }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual({ ...users[1], name: "Bob the II." }); + expect(data.value).toStrictEqual({ ...users[1], name: "Bob the II." }); app.unmount(); }); @@ -125,58 +127,58 @@ describe("with id", () => { }); it("returns user with the given id", async () => { - const [result, app] = withSetup(() => watchOne(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[0]); + expect(data.value).toStrictEqual(users[0]); app.unmount(); }); it("returns error if no user matches id", async () => { - const [result, app] = withSetup(() => watchOne(userTable, "123")); + const [{ state, data, error }, app] = withSetup(() => watchOne(userTable, "123")); await waitFor(() => { - expect(result.value.state).toBe("error"); + expect(state.value).toBe("error"); }); - expect(result.value.data).toBe(undefined); - expect(result.value.error?.message).toMatch(/No item found/); + 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 [result, app] = withSetup(() => watchOne(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); await update(userTable, { ...users[1], name: "Bob the II." }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual(users[0]); + expect(data.value).toStrictEqual(users[0]); app.unmount(); }); it("updates on changes matching the id", async () => { - const [result, app] = withSetup(() => watchOne(userTable, "0")); + const [{ state, data }, app] = withSetup(() => watchOne(userTable, "0")); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); await update(userTable, { ...users[0], name: "Alice the II." }); await waitFor(() => { - expect(result.value.state).toBe("done"); + expect(state.value).toBe("done"); }); - expect(result.value.data).toStrictEqual({ ...users[0], name: "Alice the II." }); + 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 index 4c4953a..3a9a60c 100644 --- a/packages/vue/src/watchOne.ts +++ b/packages/vue/src/watchOne.ts @@ -7,7 +7,7 @@ import { Query, Table, } from "blinkdb"; -import { computed, ComputedRef } from "vue"; +import { computed, ToRefs } from "vue"; import { QueryResult } from "./types"; import { watchMany } from "./watchMany"; @@ -27,7 +27,7 @@ import { watchMany } from "./watchMany"; export function watchOne, P extends PrimaryKeyOf>( table: Table, query: Query -): ComputedRef>; +): ToRefs>; /** * Retrieves an entity from `table` with the given `id`. @@ -41,44 +41,40 @@ export function watchOne, P extends PrimaryKeyOf>( export function watchOne, P extends PrimaryKeyOf>( table: Table, id: T[P] -): ComputedRef>; +): ToRefs>; export function watchOne, P extends PrimaryKeyOf>( table: Table, queryOrId: Query | T[P] -): ComputedRef> { - const primaryKeyProperty = key(table); - let result: ComputedRef>; +): ToRefs> { + let result: ToRefs>; + if (queryOrId === undefined) { result = watchMany(table); } else { const query = typeof queryOrId === "object" ? queryOrId - : ({ where: { [primaryKeyProperty]: queryOrId } } as unknown as Query); + : ({ where: { [key(table)]: queryOrId } } as unknown as Query); result = watchMany(table, query); } - return computed((): QueryResult => { - if (result.value.state === "done") { - if (result.value.data.length === 0) { - return { - state: "error", - data: undefined, - error: new ItemNotFoundError(queryOrId), - }; - } else if (result.value.data.length > 1) { - return { - state: "error", - data: undefined, - error: new MoreThanOneItemFoundError(queryOrId), - }; + 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 { - ...result.value, - data: result.value.data ? result.value.data[0] : undefined, - } as QueryResult; }); + + 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>; } From 3aed769af123ff98141814cb5b4deb80dfd37634 Mon Sep 17 00:00:00 2001 From: froehlichA Date: Fri, 20 Oct 2023 11:47:46 +0200 Subject: [PATCH 13/15] fix react docs --- packages/docs/src/content/docs/react.mdx | 3 ++- packages/docs/src/content/reference/useFirst.mdx | 4 ++-- packages/docs/src/content/reference/useMany.mdx | 4 ++-- packages/docs/src/content/reference/useOne.mdx | 12 ++++++------ 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/docs/src/content/docs/react.mdx b/packages/docs/src/content/docs/react.mdx index aa7348b..71874df 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/reference/useFirst.mdx b/packages/docs/src/content/reference/useFirst.mdx index 36d2712..38b7d8a 100644 --- a/packages/docs/src/content/reference/useFirst.mdx +++ b/packages/docs/src/content/reference/useFirst.mdx @@ -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 diff --git a/packages/docs/src/content/reference/useMany.mdx b/packages/docs/src/content/reference/useMany.mdx index 9e64cf7..a0be6ad 100644 --- a/packages/docs/src/content/reference/useMany.mdx +++ b/packages/docs/src/content/reference/useMany.mdx @@ -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; diff --git a/packages/docs/src/content/reference/useOne.mdx b/packages/docs/src/content/reference/useOne.mdx index de02ee6..b1e3729 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" } From 076e476193db239a37dcde473e827c1eae46bf3b Mon Sep 17 00:00:00 2001 From: froehlichA Date: Fri, 20 Oct 2023 11:48:00 +0200 Subject: [PATCH 14/15] add vue docs --- package-lock.json | 2 +- packages/docs/.astro/types.d.ts | 28 +++++ .../docs/src/components/framework-select.tsx | 7 +- packages/docs/src/content/config.ts | 2 +- packages/docs/src/content/docs/vue.mdx | 80 ++++++++++++++ .../docs/src/content/reference/watchFirst.mdx | 104 ++++++++++++++++++ .../docs/src/content/reference/watchMany.mdx | 93 ++++++++++++++++ .../docs/src/content/reference/watchOne.mdx | 95 ++++++++++++++++ packages/docs/src/pages/docs/[...slug].astro | 3 +- .../src/pages/docs/reference/[...slug].astro | 13 ++- 10 files changed, 421 insertions(+), 6 deletions(-) create mode 100644 packages/docs/src/content/docs/vue.mdx create mode 100644 packages/docs/src/content/reference/watchFirst.mdx create mode 100644 packages/docs/src/content/reference/watchMany.mdx create mode 100644 packages/docs/src/content/reference/watchOne.mdx diff --git a/package-lock.json b/package-lock.json index a78e219..5c7dbad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13362,7 +13362,7 @@ "@blinkdb/vue": { "version": "file:packages/vue", "requires": { - "@testing-library/vue": "*" + "@testing-library/vue": "^7.0.0" } }, "@cspotcode/source-map-support": { diff --git a/packages/docs/.astro/types.d.ts b/packages/docs/.astro/types.d.ts index 7fd3bd3..febec16 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": { @@ -383,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 e77a97f..e43fe7f 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 5b6563e..3b08608 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/vue.mdx b/packages/docs/src/content/docs/vue.mdx new file mode 100644 index 0000000..50f06fc --- /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/watchFirst.mdx b/packages/docs/src/content/reference/watchFirst.mdx new file mode 100644 index 0000000..46413ef --- /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 0000000..8bd369c --- /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 0000000..5983b9f --- /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 25563f5..a4138d9 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 0f0ea4b..aa67df5 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 => ( From 6a456a3df44d8575aa4fa0e3893cafff5ff96f36 Mon Sep 17 00:00:00 2001 From: froehlichA Date: Fri, 20 Oct 2023 11:52:09 +0200 Subject: [PATCH 15/15] add workflow to publish vue package --- .github/workflows/publish.yml | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7517175..d0f2a6a 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.json b/package.json index aefc8c5..faa855f 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" }, 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