mirror of
https://github.com/vrcx-team/VRCX.git
synced 2026-04-06 00:32:02 +02:00
add @tanstack/query
This commit is contained in:
144
package-lock.json
generated
144
package-lock.json
generated
@@ -7,6 +7,7 @@
|
||||
"name": "VRCX",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/vue-query": "^5.92.9",
|
||||
"hazardous": "^0.3.0",
|
||||
"node-api-dotnet": "^0.9.19"
|
||||
},
|
||||
@@ -27,6 +28,7 @@
|
||||
"@sigma/edge-curve": "^3.1.0",
|
||||
"@sigma/node-border": "^3.0.0",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"@tanstack/vue-table": "^8.21.3",
|
||||
"@tanstack/vue-virtual": "^3.13.19",
|
||||
"@types/node": "^25.3.3",
|
||||
@@ -426,7 +428,6 @@
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -436,7 +437,6 @@
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -470,7 +470,6 @@
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
|
||||
"integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.29.0"
|
||||
@@ -582,7 +581,6 @@
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
@@ -2479,7 +2477,6 @@
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
@@ -4084,6 +4081,49 @@
|
||||
"vite": "^5.2.0 || ^6 || ^7"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/match-sorter-utils": {
|
||||
"version": "8.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz",
|
||||
"integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"remove-accents": "0.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.90.20",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz",
|
||||
"integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.90.21",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz",
|
||||
"integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.90.20"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/table-core": {
|
||||
"version": "8.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
|
||||
@@ -4109,6 +4149,63 @@
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/vue-query": {
|
||||
"version": "5.92.9",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/vue-query/-/vue-query-5.92.9.tgz",
|
||||
"integrity": "sha512-jjAZcqKveyX0C4w/6zUqbnqk/XzuxNWaFsWjGTJWULVFizUNeLGME2gf9vVSDclIyiBhR13oZJPPs6fJgfpIJQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/match-sorter-utils": "^8.19.4",
|
||||
"@tanstack/query-core": "5.90.20",
|
||||
"@vue/devtools-api": "^6.6.3",
|
||||
"vue-demi": "^0.14.10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.1.2",
|
||||
"vue": "^2.6.0 || ^3.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/vue-query/node_modules/@vue/devtools-api": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tanstack/vue-query/node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/vue-table": {
|
||||
"version": "8.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.21.3.tgz",
|
||||
@@ -4627,7 +4724,6 @@
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz",
|
||||
"integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.0",
|
||||
@@ -4641,7 +4737,6 @@
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz",
|
||||
"integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.29",
|
||||
@@ -4652,7 +4747,6 @@
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz",
|
||||
"integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.0",
|
||||
@@ -4670,7 +4764,6 @@
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
@@ -4680,7 +4773,6 @@
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz",
|
||||
"integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.29",
|
||||
@@ -4727,7 +4819,6 @@
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz",
|
||||
"integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.29"
|
||||
@@ -4737,7 +4828,6 @@
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.29.tgz",
|
||||
"integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.29",
|
||||
@@ -4748,7 +4838,6 @@
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz",
|
||||
"integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.29",
|
||||
@@ -4761,7 +4850,6 @@
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.29.tgz",
|
||||
"integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.29",
|
||||
@@ -4775,7 +4863,6 @@
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.29.tgz",
|
||||
"integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/test-utils": {
|
||||
@@ -6197,7 +6284,6 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
@@ -7033,7 +7119,6 @@
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
@@ -7510,7 +7595,6 @@
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esutils": {
|
||||
@@ -9748,7 +9832,6 @@
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -10312,7 +10395,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
@@ -10370,7 +10452,6 @@
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -10578,6 +10659,17 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-binary-file-arch": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
|
||||
@@ -10661,6 +10753,12 @@
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/remove-accents": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
|
||||
"integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -11558,7 +11656,6 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -12232,7 +12329,7 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
@@ -12679,7 +12776,6 @@
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz",
|
||||
"integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"@sigma/edge-curve": "^3.1.0",
|
||||
"@sigma/node-border": "^3.0.0",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"@tanstack/vue-table": "^8.21.3",
|
||||
"@tanstack/vue-virtual": "^3.13.19",
|
||||
"@types/node": "^25.3.3",
|
||||
@@ -189,6 +190,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/vue-query": "^5.92.9",
|
||||
"hazardous": "^0.3.0",
|
||||
"node-api-dotnet": "^0.9.19"
|
||||
},
|
||||
|
||||
113
src/api/__tests__/entityQuerySync.test.js
Normal file
113
src/api/__tests__/entityQuerySync.test.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
const mockRequest = vi.fn();
|
||||
const mockPatchAndRefetchActiveQuery = vi.fn(() => Promise.resolve());
|
||||
const mockFetchWithEntityPolicy = vi.fn();
|
||||
|
||||
const mockApplyCurrentUser = vi.fn((json) => ({ id: json.id || 'usr_me', ...json }));
|
||||
const mockApplyUser = vi.fn((json) => ({ ...json }));
|
||||
const mockApplyWorld = vi.fn((json) => ({ ...json }));
|
||||
|
||||
vi.mock('../../service/request', () => ({
|
||||
request: (...args) => mockRequest(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../stores', () => ({
|
||||
useUserStore: () => ({
|
||||
currentUser: { id: 'usr_me' },
|
||||
applyCurrentUser: mockApplyCurrentUser,
|
||||
applyUser: mockApplyUser
|
||||
}),
|
||||
useWorldStore: () => ({
|
||||
applyWorld: mockApplyWorld
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../query', () => ({
|
||||
entityQueryPolicies: {
|
||||
user: { staleTime: 20000, gcTime: 90000, retry: 1, refetchOnWindowFocus: false },
|
||||
avatar: { staleTime: 60000, gcTime: 300000, retry: 1, refetchOnWindowFocus: false },
|
||||
world: { staleTime: 60000, gcTime: 300000, retry: 1, refetchOnWindowFocus: false },
|
||||
worldCollection: { staleTime: 60000, gcTime: 300000, retry: 1, refetchOnWindowFocus: false },
|
||||
instance: { staleTime: 0, gcTime: 10000, retry: 0, refetchOnWindowFocus: false }
|
||||
},
|
||||
fetchWithEntityPolicy: (...args) => mockFetchWithEntityPolicy(...args),
|
||||
patchAndRefetchActiveQuery: (...args) =>
|
||||
mockPatchAndRefetchActiveQuery(...args),
|
||||
queryKeys: {
|
||||
user: (userId) => ['user', userId],
|
||||
avatar: (avatarId) => ['avatar', avatarId],
|
||||
world: (worldId) => ['world', worldId],
|
||||
worldsByUser: (params) => ['worlds', 'user', params.userId, params],
|
||||
instance: (worldId, instanceId) => ['instance', worldId, instanceId]
|
||||
}
|
||||
}));
|
||||
|
||||
import avatarRequest from '../avatar';
|
||||
import userRequest from '../user';
|
||||
import worldRequest from '../world';
|
||||
|
||||
describe('entity mutation query sync', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('saveCurrentUser patches and refetches active user query', async () => {
|
||||
mockRequest.mockResolvedValue({ id: 'usr_me', status: 'active' });
|
||||
|
||||
await userRequest.saveCurrentUser({ status: 'active' });
|
||||
|
||||
expect(mockPatchAndRefetchActiveQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: ['user', 'usr_me']
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('saveAvatar patches and refetches active avatar query', async () => {
|
||||
mockRequest.mockResolvedValue({ id: 'avtr_1', name: 'Avatar' });
|
||||
|
||||
await avatarRequest.saveAvatar({ id: 'avtr_1', name: 'Avatar' });
|
||||
|
||||
expect(mockPatchAndRefetchActiveQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: ['avatar', 'avtr_1']
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('saveWorld patches and refetches active world query', async () => {
|
||||
mockRequest.mockResolvedValue({ id: 'wrld_1', name: 'World' });
|
||||
|
||||
await worldRequest.saveWorld({ id: 'wrld_1', name: 'World' });
|
||||
|
||||
expect(mockPatchAndRefetchActiveQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
queryKey: ['world', 'wrld_1']
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('getCachedWorlds uses policy wrapper for world list data', async () => {
|
||||
mockFetchWithEntityPolicy.mockResolvedValue({
|
||||
data: {
|
||||
json: [{ id: 'wrld_1' }],
|
||||
params: { userId: 'usr_me', n: 50, offset: 0 }
|
||||
},
|
||||
cache: true
|
||||
});
|
||||
|
||||
const args = await worldRequest.getCachedWorlds({
|
||||
userId: 'usr_me',
|
||||
n: 50,
|
||||
offset: 0,
|
||||
sort: 'updated',
|
||||
order: 'descending',
|
||||
user: 'me',
|
||||
releaseStatus: 'all'
|
||||
});
|
||||
|
||||
expect(mockFetchWithEntityPolicy).toHaveBeenCalled();
|
||||
expect(args.cache).toBe(true);
|
||||
});
|
||||
});
|
||||
91
src/api/__tests__/favoriteQuerySync.test.js
Normal file
91
src/api/__tests__/favoriteQuerySync.test.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
const mockRequest = vi.fn();
|
||||
const mockFetchWithEntityPolicy = vi.fn();
|
||||
const mockInvalidateQueries = vi.fn().mockResolvedValue();
|
||||
const mockHandleFavoriteAdd = vi.fn();
|
||||
const mockHandleFavoriteDelete = vi.fn();
|
||||
const mockHandleFavoriteGroupClear = vi.fn();
|
||||
|
||||
vi.mock('../../service/request', () => ({
|
||||
request: (...args) => mockRequest(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../stores', () => ({
|
||||
useFavoriteStore: () => ({
|
||||
handleFavoriteAdd: (...args) => mockHandleFavoriteAdd(...args),
|
||||
handleFavoriteDelete: (...args) => mockHandleFavoriteDelete(...args),
|
||||
handleFavoriteGroupClear: (...args) =>
|
||||
mockHandleFavoriteGroupClear(...args)
|
||||
}),
|
||||
useUserStore: () => ({
|
||||
currentUser: { id: 'usr_me' }
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../query', () => ({
|
||||
entityQueryPolicies: {
|
||||
favoriteCollection: {
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}
|
||||
},
|
||||
fetchWithEntityPolicy: (...args) => mockFetchWithEntityPolicy(...args),
|
||||
queryClient: {
|
||||
invalidateQueries: (...args) => mockInvalidateQueries(...args)
|
||||
},
|
||||
queryKeys: {
|
||||
favoriteLimits: () => ['favorite', 'limits'],
|
||||
favorites: (params) => ['favorite', 'items', params],
|
||||
favoriteGroups: (params) => ['favorite', 'groups', params],
|
||||
favoriteWorlds: (params) => ['favorite', 'worlds', params],
|
||||
favoriteAvatars: (params) => ['favorite', 'avatars', params]
|
||||
}
|
||||
}));
|
||||
|
||||
import favoriteRequest from '../favorite';
|
||||
|
||||
describe('favorite query sync', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('cached favorite reads go through fetchWithEntityPolicy', async () => {
|
||||
mockFetchWithEntityPolicy.mockResolvedValue({
|
||||
data: { json: [], params: { n: 300, offset: 0 } },
|
||||
cache: true
|
||||
});
|
||||
|
||||
const args = await favoriteRequest.getCachedFavorites({
|
||||
n: 300,
|
||||
offset: 0
|
||||
});
|
||||
|
||||
expect(mockFetchWithEntityPolicy).toHaveBeenCalled();
|
||||
expect(args.cache).toBe(true);
|
||||
});
|
||||
|
||||
test('favorite mutations invalidate active favorite queries', async () => {
|
||||
mockRequest.mockResolvedValue({ ok: true });
|
||||
|
||||
await favoriteRequest.addFavorite({ type: 'world', favoriteId: 'wrld_1' });
|
||||
await favoriteRequest.deleteFavorite({ objectId: 'fav_1' });
|
||||
await favoriteRequest.saveFavoriteGroup({
|
||||
type: 'world',
|
||||
group: 'worlds1',
|
||||
displayName: 'Worlds'
|
||||
});
|
||||
await favoriteRequest.clearFavoriteGroup({
|
||||
type: 'world',
|
||||
group: 'worlds1'
|
||||
});
|
||||
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledTimes(4);
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ['favorite'],
|
||||
refetchType: 'active'
|
||||
});
|
||||
});
|
||||
});
|
||||
72
src/api/__tests__/friendQuerySync.test.js
Normal file
72
src/api/__tests__/friendQuerySync.test.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
const mockRequest = vi.fn();
|
||||
const mockFetchWithEntityPolicy = vi.fn();
|
||||
const mockInvalidateQueries = vi.fn().mockResolvedValue();
|
||||
const mockApplyUser = vi.fn((json) => json);
|
||||
|
||||
vi.mock('../../service/request', () => ({
|
||||
request: (...args) => mockRequest(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../stores/user', () => ({
|
||||
useUserStore: () => ({
|
||||
applyUser: (...args) => mockApplyUser(...args)
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../query', () => ({
|
||||
entityQueryPolicies: {
|
||||
friendList: {
|
||||
staleTime: 20000,
|
||||
gcTime: 90000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}
|
||||
},
|
||||
fetchWithEntityPolicy: (...args) => mockFetchWithEntityPolicy(...args),
|
||||
queryClient: {
|
||||
invalidateQueries: (...args) => mockInvalidateQueries(...args)
|
||||
},
|
||||
queryKeys: {
|
||||
friends: (params) => ['friends', params]
|
||||
}
|
||||
}));
|
||||
|
||||
import friendRequest from '../friend';
|
||||
|
||||
describe('friend query sync', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('getCachedFriends uses query policy wrapper', async () => {
|
||||
mockFetchWithEntityPolicy.mockResolvedValue({
|
||||
data: {
|
||||
json: [{ id: 'usr_1', displayName: 'A' }],
|
||||
params: { n: 50, offset: 0 }
|
||||
},
|
||||
cache: true
|
||||
});
|
||||
|
||||
const args = await friendRequest.getCachedFriends({ n: 50, offset: 0 });
|
||||
|
||||
expect(mockFetchWithEntityPolicy).toHaveBeenCalled();
|
||||
expect(args.cache).toBe(true);
|
||||
expect(args.json[0].id).toBe('usr_1');
|
||||
});
|
||||
|
||||
test('friend mutations invalidate active friends queries', async () => {
|
||||
mockRequest.mockResolvedValue({ ok: true });
|
||||
|
||||
await friendRequest.sendFriendRequest({ userId: 'usr_1' });
|
||||
await friendRequest.cancelFriendRequest({ userId: 'usr_1' });
|
||||
await friendRequest.deleteFriend({ userId: 'usr_1' });
|
||||
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledTimes(3);
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ['friends'],
|
||||
refetchType: 'active'
|
||||
});
|
||||
});
|
||||
});
|
||||
101
src/api/__tests__/groupQuerySync.test.js
Normal file
101
src/api/__tests__/groupQuerySync.test.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
const mockRequest = vi.fn();
|
||||
const mockFetchWithEntityPolicy = vi.fn();
|
||||
const mockInvalidateQueries = vi.fn().mockResolvedValue();
|
||||
const mockApplyGroup = vi.fn((json) => json);
|
||||
|
||||
vi.mock('../../service/request', () => ({
|
||||
request: (...args) => mockRequest(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../stores', () => ({
|
||||
useGroupStore: () => ({
|
||||
applyGroup: (...args) => mockApplyGroup(...args)
|
||||
}),
|
||||
useUserStore: () => ({
|
||||
currentUser: { id: 'usr_me' }
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../query', () => ({
|
||||
entityQueryPolicies: {
|
||||
group: {
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
},
|
||||
groupCollection: {
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}
|
||||
},
|
||||
fetchWithEntityPolicy: (...args) => mockFetchWithEntityPolicy(...args),
|
||||
queryClient: {
|
||||
invalidateQueries: (...args) => mockInvalidateQueries(...args)
|
||||
},
|
||||
queryKeys: {
|
||||
group: (groupId, includeRoles) => ['group', groupId, Boolean(includeRoles)],
|
||||
groupPosts: (params) => ['group', params.groupId, 'posts', params],
|
||||
groupMember: (params) => ['group', params.groupId, 'member', params.userId],
|
||||
groupMembers: (params) => ['group', params.groupId, 'members', params],
|
||||
groupGallery: (params) => ['group', params.groupId, 'gallery', params.galleryId, params],
|
||||
groupCalendar: (groupId) => ['group', groupId, 'calendar'],
|
||||
groupCalendarEvent: (params) => ['group', params.groupId, 'calendarEvent', params.eventId]
|
||||
}
|
||||
}));
|
||||
|
||||
import groupRequest from '../group';
|
||||
|
||||
describe('group query sync', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('cached group resources use fetchWithEntityPolicy', async () => {
|
||||
mockFetchWithEntityPolicy.mockResolvedValue({
|
||||
data: { json: [], params: { groupId: 'grp_1', n: 100, offset: 0 } },
|
||||
cache: true
|
||||
});
|
||||
|
||||
const a = await groupRequest.getCachedGroupMembers({
|
||||
groupId: 'grp_1',
|
||||
n: 100,
|
||||
offset: 0,
|
||||
sort: 'joinedAt:desc'
|
||||
});
|
||||
const b = await groupRequest.getCachedGroupGallery({
|
||||
groupId: 'grp_1',
|
||||
galleryId: 'gal_1',
|
||||
n: 100,
|
||||
offset: 0
|
||||
});
|
||||
|
||||
expect(mockFetchWithEntityPolicy).toHaveBeenCalledTimes(2);
|
||||
expect(a.cache && b.cache).toBe(true);
|
||||
});
|
||||
|
||||
test('group mutations invalidate scoped active group queries', async () => {
|
||||
mockRequest.mockResolvedValue({ ok: true });
|
||||
|
||||
await groupRequest.setGroupRepresentation('grp_1', {
|
||||
isRepresenting: true
|
||||
});
|
||||
await groupRequest.deleteGroupPost({
|
||||
groupId: 'grp_1',
|
||||
postId: 'post_1'
|
||||
});
|
||||
await groupRequest.setGroupMemberProps('usr_me', 'grp_1', {
|
||||
visibility: 'visible'
|
||||
});
|
||||
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledTimes(3);
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ['group', 'grp_1'],
|
||||
refetchType: 'active'
|
||||
});
|
||||
});
|
||||
});
|
||||
100
src/api/__tests__/mediaQuerySync.test.js
Normal file
100
src/api/__tests__/mediaQuerySync.test.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
const mockRequest = vi.fn();
|
||||
const mockFetchWithEntityPolicy = vi.fn();
|
||||
const mockInvalidateQueries = vi.fn().mockResolvedValue();
|
||||
const mockRemoveQueries = vi.fn();
|
||||
|
||||
vi.mock('../../service/request', () => ({
|
||||
request: (...args) => mockRequest(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../stores', () => ({
|
||||
useUserStore: () => ({
|
||||
currentUser: { id: 'usr_me' }
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../query', () => ({
|
||||
entityQueryPolicies: {
|
||||
galleryCollection: {
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
},
|
||||
inventoryCollection: {
|
||||
staleTime: 20000,
|
||||
gcTime: 120000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
},
|
||||
fileObject: {
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}
|
||||
},
|
||||
fetchWithEntityPolicy: (...args) => mockFetchWithEntityPolicy(...args),
|
||||
queryClient: {
|
||||
invalidateQueries: (...args) => mockInvalidateQueries(...args),
|
||||
removeQueries: (...args) => mockRemoveQueries(...args)
|
||||
},
|
||||
queryKeys: {
|
||||
galleryFiles: (params) => ['gallery', 'files', params],
|
||||
prints: (params) => ['gallery', 'prints', params],
|
||||
print: (printId) => ['gallery', 'print', printId],
|
||||
inventoryItems: (params) => ['inventory', 'items', params],
|
||||
userInventoryItem: (params) => ['inventory', 'item', params.userId, params.inventoryId],
|
||||
file: (fileId) => ['file', fileId]
|
||||
}
|
||||
}));
|
||||
|
||||
import inventoryRequest from '../inventory';
|
||||
import miscRequest from '../misc';
|
||||
import vrcPlusIconRequest from '../vrcPlusIcon';
|
||||
import vrcPlusImageRequest from '../vrcPlusImage';
|
||||
|
||||
describe('media and inventory query sync', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('cached media/inventory reads go through fetchWithEntityPolicy', async () => {
|
||||
mockFetchWithEntityPolicy.mockResolvedValue({
|
||||
data: { json: [], params: {} },
|
||||
cache: true
|
||||
});
|
||||
|
||||
const a = await vrcPlusIconRequest.getCachedFileList({ tag: 'icon', n: 100 });
|
||||
const b = await vrcPlusImageRequest.getCachedPrints({ n: 100 });
|
||||
const c = await inventoryRequest.getCachedInventoryItems({
|
||||
n: 100,
|
||||
offset: 0,
|
||||
order: 'newest'
|
||||
});
|
||||
const d = await miscRequest.getCachedFile({ fileId: 'file_1' });
|
||||
|
||||
expect(mockFetchWithEntityPolicy).toHaveBeenCalledTimes(4);
|
||||
expect(a.cache && b.cache && c.cache && d.cache).toBe(true);
|
||||
});
|
||||
|
||||
test('media mutations invalidate gallery queries and file delete removes file query', async () => {
|
||||
mockRequest.mockResolvedValue({ ok: true });
|
||||
|
||||
await vrcPlusIconRequest.deleteFile('file_icon_1');
|
||||
await vrcPlusImageRequest.deletePrint('print_1');
|
||||
await vrcPlusImageRequest.uploadEmoji('img', { tag: 'emoji' });
|
||||
await miscRequest.deleteFile('file_misc_1');
|
||||
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ['gallery'],
|
||||
refetchType: 'active'
|
||||
});
|
||||
expect(mockRemoveQueries).toHaveBeenCalledWith({
|
||||
queryKey: ['file', 'file_misc_1'],
|
||||
exact: true
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,11 @@
|
||||
import { request } from '../service/request';
|
||||
import { useUserStore } from '../stores';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
patchAndRefetchActiveQuery,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
const avatarReq = {
|
||||
/**
|
||||
@@ -17,6 +23,22 @@ const avatarReq = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch avatar from query cache if fresh. Otherwise, calls API.
|
||||
* @param {{avatarId: string}} params
|
||||
* @returns {Promise<{json: any, ref?: any, cache?: boolean, params: {avatarId: string}}>}
|
||||
*/
|
||||
getCachedAvatar(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.avatar(params.avatarId),
|
||||
policy: entityQueryPolicies.avatar,
|
||||
queryFn: () => avatarReq.getAvatar(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {import('../types/api/avatar').GetAvatars}
|
||||
*/
|
||||
@@ -46,6 +68,12 @@ const avatarReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
patchAndRefetchActiveQuery({
|
||||
queryKey: queryKeys.avatar(params.id),
|
||||
nextData: args
|
||||
}).catch((err) => {
|
||||
console.error('Failed to refresh avatar query after mutation:', err);
|
||||
});
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -64,7 +92,20 @@ const avatarReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
userStore.applyCurrentUser(json);
|
||||
const ref = userStore.applyCurrentUser(json);
|
||||
patchAndRefetchActiveQuery({
|
||||
queryKey: queryKeys.user(ref.id),
|
||||
nextData: {
|
||||
json,
|
||||
params: { userId: ref.id },
|
||||
ref
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(
|
||||
'Failed to refresh current user query after avatar select:',
|
||||
err
|
||||
);
|
||||
});
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -83,7 +124,20 @@ const avatarReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
userStore.applyCurrentUser(json);
|
||||
const ref = userStore.applyCurrentUser(json);
|
||||
patchAndRefetchActiveQuery({
|
||||
queryKey: queryKeys.user(ref.id),
|
||||
nextData: {
|
||||
json,
|
||||
params: { userId: ref.id },
|
||||
ref
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(
|
||||
'Failed to refresh current user query after fallback avatar select:',
|
||||
err
|
||||
);
|
||||
});
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import { useFavoriteStore, useUserStore } from '../stores';
|
||||
import { request } from '../service/request';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
queryClient,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
function getCurrentUserId() {
|
||||
return useUserStore().currentUser.id;
|
||||
}
|
||||
|
||||
function refetchActiveFavoriteQueries() {
|
||||
queryClient
|
||||
.invalidateQueries({
|
||||
queryKey: ['favorite'],
|
||||
refetchType: 'active'
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to refresh favorite queries:', err);
|
||||
});
|
||||
}
|
||||
|
||||
const favoriteReq = {
|
||||
getFavoriteLimits() {
|
||||
return request('auth/user/favoritelimits', {
|
||||
@@ -17,6 +34,17 @@ const favoriteReq = {
|
||||
});
|
||||
},
|
||||
|
||||
getCachedFavoriteLimits() {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.favoriteLimits(),
|
||||
policy: entityQueryPolicies.favoriteCollection,
|
||||
queryFn: () => favoriteReq.getFavoriteLimits()
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {import('../types/api/favorite').GetFavorites}
|
||||
*/
|
||||
@@ -33,6 +61,17 @@ const favoriteReq = {
|
||||
});
|
||||
},
|
||||
|
||||
getCachedFavorites(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.favorites(params),
|
||||
policy: entityQueryPolicies.favoriteCollection,
|
||||
queryFn: () => favoriteReq.getFavorites(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {import('../types/api/favorite').AddFavorite}
|
||||
*/
|
||||
@@ -46,6 +85,7 @@ const favoriteReq = {
|
||||
params
|
||||
};
|
||||
useFavoriteStore().handleFavoriteAdd(args);
|
||||
refetchActiveFavoriteQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -63,6 +103,7 @@ const favoriteReq = {
|
||||
params
|
||||
};
|
||||
useFavoriteStore().handleFavoriteDelete(params.objectId);
|
||||
refetchActiveFavoriteQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -84,6 +125,17 @@ const favoriteReq = {
|
||||
});
|
||||
},
|
||||
|
||||
getCachedFavoriteGroups(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.favoriteGroups(params),
|
||||
policy: entityQueryPolicies.favoriteCollection,
|
||||
queryFn: () => favoriteReq.getFavoriteGroups(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{ type: string, group: string, displayName?: string, visibility?: string }} params group is a name
|
||||
@@ -101,6 +153,7 @@ const favoriteReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveFavoriteQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -125,6 +178,7 @@ const favoriteReq = {
|
||||
params
|
||||
};
|
||||
useFavoriteStore().handleFavoriteGroupClear(args);
|
||||
refetchActiveFavoriteQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -145,6 +199,17 @@ const favoriteReq = {
|
||||
});
|
||||
},
|
||||
|
||||
getCachedFavoriteWorlds(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.favoriteWorlds(params),
|
||||
policy: entityQueryPolicies.favoriteCollection,
|
||||
queryFn: () => favoriteReq.getFavoriteWorlds(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {import('../types/api/favorite').GetFavoriteAvatars}
|
||||
*/
|
||||
@@ -159,6 +224,17 @@ const favoriteReq = {
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
getCachedFavoriteAvatars(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.favoriteAvatars(params),
|
||||
policy: entityQueryPolicies.favoriteCollection,
|
||||
queryFn: () => favoriteReq.getFavoriteAvatars(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
import { request } from '../service/request';
|
||||
import { useUserStore } from '../stores/user';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
queryClient,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
function refetchActiveFriendListQueries() {
|
||||
queryClient
|
||||
.invalidateQueries({
|
||||
queryKey: ['friends'],
|
||||
refetchType: 'active'
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to refresh friend list queries:', err);
|
||||
});
|
||||
}
|
||||
|
||||
const friendReq = {
|
||||
/**
|
||||
@@ -27,6 +44,22 @@ const friendReq = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch friends from query cache if still fresh. Otherwise, calls API.
|
||||
* @param {{ n: number, offset: number, offline?: boolean }} params
|
||||
* @returns {Promise<{json: any, params: { n: number, offset: number, offline?: boolean }, cache?: boolean}>}
|
||||
*/
|
||||
getCachedFriends(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.friends(params),
|
||||
policy: entityQueryPolicies.friendList,
|
||||
queryFn: () => friendReq.getFriends(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{ userId: string }} params
|
||||
* @returns {Promise<{json: any, params: { userId: string }}>}
|
||||
@@ -39,6 +72,7 @@ const friendReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveFriendListQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -55,6 +89,7 @@ const friendReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveFriendListQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -72,6 +107,7 @@ const friendReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveFriendListQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
130
src/api/group.js
130
src/api/group.js
@@ -1,9 +1,29 @@
|
||||
import { useGroupStore, useUserStore } from '../stores';
|
||||
import { request } from '../service/request';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
queryClient,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
function getCurrentUserId() {
|
||||
return useUserStore().currentUser.id;
|
||||
}
|
||||
|
||||
function refetchActiveGroupScope(groupId) {
|
||||
if (!groupId) {
|
||||
return;
|
||||
}
|
||||
queryClient
|
||||
.invalidateQueries({
|
||||
queryKey: ['group', groupId],
|
||||
refetchType: 'active'
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to refresh scoped group queries:', err);
|
||||
});
|
||||
}
|
||||
const groupReq = {
|
||||
/**
|
||||
* @param {string} groupId
|
||||
@@ -20,6 +40,7 @@ const groupReq = {
|
||||
groupId,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -52,6 +73,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -78,26 +100,18 @@ const groupReq = {
|
||||
* @return { Promise<{json: any, ref: any, cache?: boolean, params}> }
|
||||
*/
|
||||
getCachedGroup(params) {
|
||||
const groupStore = useGroupStore();
|
||||
return new Promise((resolve, reject) => {
|
||||
const ref = groupStore.cachedGroups.get(params.groupId);
|
||||
if (typeof ref === 'undefined') {
|
||||
groupReq
|
||||
.getGroup(params)
|
||||
.then((args) => {
|
||||
args.ref = groupStore.applyGroup(args.json);
|
||||
resolve(args);
|
||||
})
|
||||
.catch(reject);
|
||||
} else {
|
||||
resolve({
|
||||
cache: true,
|
||||
json: ref,
|
||||
params,
|
||||
ref
|
||||
});
|
||||
}
|
||||
});
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.group(params.groupId, params.includeRoles),
|
||||
policy: entityQueryPolicies.group,
|
||||
queryFn: () => groupReq.getGroup(params).then((args) => {
|
||||
const groupStore = useGroupStore();
|
||||
args.ref = groupStore.applyGroup(args.json);
|
||||
return args;
|
||||
})
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* @param {{ userId: string }} params
|
||||
@@ -141,6 +155,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -156,6 +171,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -195,6 +211,7 @@ const groupReq = {
|
||||
groupId,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -217,6 +234,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -239,6 +257,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -273,6 +292,16 @@ const groupReq = {
|
||||
return args;
|
||||
});
|
||||
},
|
||||
getCachedGroupPosts(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.groupPosts(params),
|
||||
policy: entityQueryPolicies.groupCollection,
|
||||
queryFn: () => groupReq.getGroupPosts(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
editGroupPost(params) {
|
||||
return request(`groups/${params.groupId}/posts/${params.postId}`, {
|
||||
method: 'PUT',
|
||||
@@ -282,6 +311,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -294,6 +324,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -315,6 +346,16 @@ const groupReq = {
|
||||
return args;
|
||||
});
|
||||
},
|
||||
getCachedGroupMember(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.groupMember(params),
|
||||
policy: entityQueryPolicies.groupCollection,
|
||||
queryFn: () => groupReq.getGroupMember(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* @param {{
|
||||
* groupId: string,
|
||||
@@ -335,6 +376,16 @@ const groupReq = {
|
||||
return args;
|
||||
});
|
||||
},
|
||||
getCachedGroupMembers(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.groupMembers(params),
|
||||
policy: entityQueryPolicies.groupCollection,
|
||||
queryFn: () => groupReq.getGroupMembers(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* @param {{
|
||||
* groupId: string,
|
||||
@@ -370,6 +421,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -388,6 +440,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -427,6 +480,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -445,6 +499,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -456,6 +511,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -496,6 +552,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -510,7 +567,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -526,6 +583,7 @@ const groupReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGroupScope(params.groupId);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -698,6 +756,16 @@ const groupReq = {
|
||||
return args;
|
||||
});
|
||||
},
|
||||
getCachedGroupGallery(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.groupGallery(params),
|
||||
policy: entityQueryPolicies.groupCollection,
|
||||
queryFn: () => groupReq.getGroupGallery(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
getGroupCalendar(groupId) {
|
||||
return request(`calendar/${groupId}`, {
|
||||
@@ -712,6 +780,16 @@ const groupReq = {
|
||||
return args;
|
||||
});
|
||||
},
|
||||
getCachedGroupCalendar(groupId) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.groupCalendar(groupId),
|
||||
policy: entityQueryPolicies.groupCollection,
|
||||
queryFn: () => groupReq.getGroupCalendar(groupId)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
@@ -731,6 +809,16 @@ const groupReq = {
|
||||
return args;
|
||||
});
|
||||
},
|
||||
getCachedGroupCalendarEvent(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.groupCalendarEvent(params),
|
||||
policy: entityQueryPolicies.groupCollection,
|
||||
queryFn: () => groupReq.getGroupCalendarEvent(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {import('../types/api/group').GetCalendars}
|
||||
|
||||
@@ -3,6 +3,12 @@ import { toast } from 'vue-sonner';
|
||||
import { i18n } from '../plugin/i18n';
|
||||
import { request } from '../service/request';
|
||||
import { useInstanceStore } from '../stores';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
patchAndRefetchActiveQuery,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
const instanceReq = {
|
||||
/**
|
||||
@@ -27,28 +33,14 @@ const instanceReq = {
|
||||
* @returns {Promise<{json: any, ref: any, cache?: boolean, params}>}
|
||||
*/
|
||||
getCachedInstance(params) {
|
||||
const instanceStore = useInstanceStore();
|
||||
return new Promise((resolve, reject) => {
|
||||
const ref = instanceStore.cachedInstances.get(
|
||||
`${params.worldId}:${params.instanceId}`
|
||||
);
|
||||
if (typeof ref === 'undefined') {
|
||||
instanceReq
|
||||
.getInstance(params)
|
||||
.then((args) => {
|
||||
args.ref = instanceStore.applyInstance(args.json);
|
||||
resolve(args);
|
||||
})
|
||||
.catch(reject);
|
||||
} else {
|
||||
resolve({
|
||||
cache: true,
|
||||
json: ref,
|
||||
params,
|
||||
ref
|
||||
});
|
||||
}
|
||||
});
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.instance(params.worldId, params.instanceId),
|
||||
policy: entityQueryPolicies.instance,
|
||||
queryFn: () => instanceReq.getInstance(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -65,6 +57,15 @@ const instanceReq = {
|
||||
params
|
||||
};
|
||||
args.ref = instanceStore.applyInstance(json);
|
||||
patchAndRefetchActiveQuery({
|
||||
queryKey: queryKeys.instance(args.ref.worldId, args.ref.instanceId),
|
||||
nextData: args
|
||||
}).catch((err) => {
|
||||
console.error(
|
||||
'Failed to refresh instance query after instance creation:',
|
||||
err
|
||||
);
|
||||
});
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -107,6 +108,15 @@ const instanceReq = {
|
||||
params
|
||||
};
|
||||
args.ref = instanceStore.applyInstance(json);
|
||||
patchAndRefetchActiveQuery({
|
||||
queryKey: queryKeys.instance(args.ref.worldId, args.ref.instanceId),
|
||||
nextData: args
|
||||
}).catch((err) => {
|
||||
console.error(
|
||||
'Failed to refresh instance query after short-name resolve:',
|
||||
err
|
||||
);
|
||||
});
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
import { request } from '../service/request';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
queryClient,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
function refetchActiveInventoryQueries() {
|
||||
queryClient
|
||||
.invalidateQueries({
|
||||
queryKey: ['inventory'],
|
||||
refetchType: 'active'
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to refresh inventory queries:', err);
|
||||
});
|
||||
}
|
||||
|
||||
const inventoryReq = {
|
||||
/**
|
||||
@@ -20,6 +37,17 @@ const inventoryReq = {
|
||||
});
|
||||
},
|
||||
|
||||
getCachedUserInventoryItem(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.userInventoryItem(params),
|
||||
policy: entityQueryPolicies.inventoryCollection,
|
||||
queryFn: () => inventoryReq.getUserInventoryItem(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{ inventoryId: string }} params
|
||||
* @returns {Promise<{json: any, params}>}
|
||||
@@ -54,6 +82,17 @@ const inventoryReq = {
|
||||
});
|
||||
},
|
||||
|
||||
getCachedInventoryItems(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.inventoryItems(params),
|
||||
policy: entityQueryPolicies.inventoryCollection,
|
||||
queryFn: () => inventoryReq.getInventoryItems(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{ inventoryId: string }} params
|
||||
* @returns {Promise<{json: any, params}>}
|
||||
@@ -67,6 +106,7 @@ const inventoryReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveInventoryQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -102,6 +142,7 @@ const inventoryReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveInventoryQueries();
|
||||
return args;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { request } from '../service/request';
|
||||
import { useUserStore } from '../stores';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
queryClient,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
function getCurrentUserId() {
|
||||
return useUserStore().currentUser.id;
|
||||
@@ -18,6 +24,17 @@ const miscReq = {
|
||||
});
|
||||
},
|
||||
|
||||
getCachedFile(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.file(params.fileId),
|
||||
policy: entityQueryPolicies.fileObject,
|
||||
queryFn: () => miscReq.getFile(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
saveNote(params) {
|
||||
return request('userNotes', {
|
||||
method: 'POST',
|
||||
@@ -192,6 +209,10 @@ const miscReq = {
|
||||
json,
|
||||
fileId
|
||||
};
|
||||
queryClient.removeQueries({
|
||||
queryKey: queryKeys.file(fileId),
|
||||
exact: true
|
||||
});
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { request } from '../service/request';
|
||||
import { useUserStore } from '../stores';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
patchAndRefetchActiveQuery,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
@@ -39,26 +45,14 @@ const userReq = {
|
||||
* @type {import('../types/api/user').GetCachedUser}
|
||||
*/
|
||||
getCachedUser(params) {
|
||||
const userStore = useUserStore();
|
||||
return new Promise((resolve, reject) => {
|
||||
const ref = userStore.cachedUsers.get(params.userId);
|
||||
if (typeof ref === 'undefined') {
|
||||
userReq
|
||||
.getUser(params)
|
||||
.then((args) => {
|
||||
args.ref = userStore.applyUser(args.json);
|
||||
resolve(args);
|
||||
})
|
||||
.catch(reject);
|
||||
} else {
|
||||
resolve({
|
||||
cache: true,
|
||||
json: ref,
|
||||
params,
|
||||
ref
|
||||
});
|
||||
}
|
||||
});
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.user(params.userId),
|
||||
policy: entityQueryPolicies.user,
|
||||
queryFn: () => userReq.getUser(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -149,6 +143,12 @@ const userReq = {
|
||||
params,
|
||||
ref: userStore.applyCurrentUser(json)
|
||||
};
|
||||
patchAndRefetchActiveQuery({
|
||||
queryKey: queryKeys.user(args.ref.id),
|
||||
nextData: args
|
||||
}).catch((err) => {
|
||||
console.error('Failed to refresh user query after mutation:', err);
|
||||
});
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
import { request } from '../service/request';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
queryClient,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
function refetchActiveGalleryQueries() {
|
||||
queryClient
|
||||
.invalidateQueries({
|
||||
queryKey: ['gallery'],
|
||||
refetchType: 'active'
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to refresh gallery queries:', err);
|
||||
});
|
||||
}
|
||||
|
||||
const VRCPlusIconsReq = {
|
||||
getFileList(params) {
|
||||
@@ -14,6 +31,17 @@ const VRCPlusIconsReq = {
|
||||
});
|
||||
},
|
||||
|
||||
getCachedFileList(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.galleryFiles(params),
|
||||
policy: entityQueryPolicies.galleryCollection,
|
||||
queryFn: () => VRCPlusIconsReq.getFileList(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
deleteFile(fileId) {
|
||||
return request(`file/${fileId}`, {
|
||||
method: 'DELETE'
|
||||
@@ -22,6 +50,7 @@ const VRCPlusIconsReq = {
|
||||
json,
|
||||
fileId
|
||||
};
|
||||
refetchActiveGalleryQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -40,6 +69,7 @@ const VRCPlusIconsReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGalleryQueries();
|
||||
return args;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
import { request } from '../service/request';
|
||||
import { useUserStore } from '../stores';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
queryClient,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
function getCurrentUserId() {
|
||||
return useUserStore().currentUser.id;
|
||||
}
|
||||
|
||||
function refetchActiveGalleryQueries() {
|
||||
queryClient
|
||||
.invalidateQueries({
|
||||
queryKey: ['gallery'],
|
||||
refetchType: 'active'
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to refresh gallery queries:', err);
|
||||
});
|
||||
}
|
||||
const vrcPlusImageReq = {
|
||||
uploadGalleryImage(imageData) {
|
||||
const params = {
|
||||
@@ -19,6 +36,7 @@ const vrcPlusImageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGalleryQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -34,6 +52,7 @@ const vrcPlusImageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGalleryQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -51,6 +70,17 @@ const vrcPlusImageReq = {
|
||||
});
|
||||
},
|
||||
|
||||
getCachedPrints(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.prints(params),
|
||||
policy: entityQueryPolicies.galleryCollection,
|
||||
queryFn: () => vrcPlusImageReq.getPrints(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
deletePrint(printId) {
|
||||
return request(`prints/${printId}`, {
|
||||
method: 'DELETE'
|
||||
@@ -59,6 +89,7 @@ const vrcPlusImageReq = {
|
||||
json,
|
||||
printId
|
||||
};
|
||||
refetchActiveGalleryQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -74,6 +105,7 @@ const vrcPlusImageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGalleryQueries();
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -90,6 +122,17 @@ const vrcPlusImageReq = {
|
||||
});
|
||||
},
|
||||
|
||||
getCachedPrint(params) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.print(params.printId),
|
||||
policy: entityQueryPolicies.galleryCollection,
|
||||
queryFn: () => vrcPlusImageReq.getPrint(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
uploadEmoji(imageData, params) {
|
||||
return request('file/image', {
|
||||
uploadImage: true,
|
||||
@@ -101,6 +144,7 @@ const vrcPlusImageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
refetchActiveGalleryQueries();
|
||||
return args;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { request } from '../service/request';
|
||||
import { useWorldStore } from '../stores';
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
fetchWithEntityPolicy,
|
||||
patchAndRefetchActiveQuery,
|
||||
queryKeys
|
||||
} from '../query';
|
||||
|
||||
const worldReq = {
|
||||
/**
|
||||
@@ -24,26 +30,14 @@ const worldReq = {
|
||||
* @returns {Promise<{json: any, ref: any, cache?: boolean, params}>}
|
||||
*/
|
||||
getCachedWorld(params) {
|
||||
const worldStore = useWorldStore();
|
||||
return new Promise((resolve, reject) => {
|
||||
const ref = worldStore.cachedWorlds.get(params.worldId);
|
||||
if (typeof ref === 'undefined') {
|
||||
worldReq
|
||||
.getWorld(params)
|
||||
.then((args) => {
|
||||
args.ref = worldStore.applyWorld(args.json);
|
||||
resolve(args);
|
||||
})
|
||||
.catch(reject);
|
||||
} else {
|
||||
resolve({
|
||||
cache: true,
|
||||
json: ref,
|
||||
params,
|
||||
ref
|
||||
});
|
||||
}
|
||||
});
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.world(params.worldId),
|
||||
policy: entityQueryPolicies.world,
|
||||
queryFn: () => worldReq.getWorld(params)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -70,6 +64,24 @@ const worldReq = {
|
||||
return args;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {string} [option]
|
||||
* @returns {Promise<{json: any, cache?: boolean, params: any, option?: string}>}
|
||||
*/
|
||||
getCachedWorlds(params, option) {
|
||||
return fetchWithEntityPolicy({
|
||||
queryKey: queryKeys.worldsByUser({
|
||||
...params,
|
||||
option: option || ''
|
||||
}),
|
||||
policy: entityQueryPolicies.worldCollection,
|
||||
queryFn: () => worldReq.getWorlds(params, option)
|
||||
}).then(({ data, cache }) => ({
|
||||
...data,
|
||||
cache
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* @param {{worldId: string}} params
|
||||
* @returns {Promise<{json: any, params}>}
|
||||
@@ -100,6 +112,12 @@ const worldReq = {
|
||||
params
|
||||
};
|
||||
args.ref = worldStore.applyWorld(json);
|
||||
patchAndRefetchActiveQuery({
|
||||
queryKey: queryKeys.world(args.ref.id),
|
||||
nextData: args
|
||||
}).catch((err) => {
|
||||
console.error('Failed to refresh world query after mutation:', err);
|
||||
});
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -119,6 +137,12 @@ const worldReq = {
|
||||
params
|
||||
};
|
||||
args.ref = worldStore.applyWorld(json);
|
||||
patchAndRefetchActiveQuery({
|
||||
queryKey: queryKeys.world(args.ref.id),
|
||||
nextData: args
|
||||
}).catch((err) => {
|
||||
console.error('Failed to refresh world query after publish:', err);
|
||||
});
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -138,6 +162,12 @@ const worldReq = {
|
||||
params
|
||||
};
|
||||
args.ref = worldStore.applyWorld(json);
|
||||
patchAndRefetchActiveQuery({
|
||||
queryKey: queryKeys.world(args.ref.id),
|
||||
nextData: args
|
||||
}).catch((err) => {
|
||||
console.error('Failed to refresh world query after unpublish:', err);
|
||||
});
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createApp } from 'vue';
|
||||
import { VueQueryPlugin } from '@tanstack/vue-query';
|
||||
|
||||
import {
|
||||
i18n,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
initSentry
|
||||
} from './plugin';
|
||||
import { initPiniaPlugins, pinia } from './stores';
|
||||
import { queryClient } from './query';
|
||||
|
||||
import App from './App.vue';
|
||||
|
||||
@@ -18,7 +20,7 @@ await initPiniaPlugins();
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(pinia).use(i18n);
|
||||
app.use(pinia).use(i18n).use(VueQueryPlugin, { queryClient });
|
||||
initComponents(app);
|
||||
initRouter(app);
|
||||
await initSentry(app);
|
||||
|
||||
@@ -799,8 +799,7 @@
|
||||
switch (command) {
|
||||
case 'Refresh':
|
||||
const avatarId = D.id;
|
||||
D.id = '';
|
||||
showAvatarDialog(avatarId);
|
||||
showAvatarDialog(avatarId, { forceRefresh: true });
|
||||
break;
|
||||
case 'Share':
|
||||
copyAvatarUrl(D.id);
|
||||
@@ -1038,8 +1037,7 @@
|
||||
toast.success(t('message.upload.success'));
|
||||
// force refresh cover image
|
||||
const avatarId = avatarDialog.value.id;
|
||||
avatarDialog.value.id = '';
|
||||
showAvatarDialog(avatarId);
|
||||
showAvatarDialog(avatarId, { forceRefresh: true });
|
||||
} catch (error) {
|
||||
console.error('avatar image upload process failed:', error);
|
||||
toast.error(t('message.upload.error'));
|
||||
|
||||
@@ -1537,8 +1537,7 @@
|
||||
break;
|
||||
case 'Refresh':
|
||||
const groupId = D.id;
|
||||
D.id = '';
|
||||
showGroupDialog(groupId);
|
||||
showGroupDialog(groupId, { forceRefresh: true });
|
||||
break;
|
||||
case 'Leave Group':
|
||||
leaveGroupPrompt(D.id);
|
||||
@@ -1713,7 +1712,7 @@
|
||||
}
|
||||
if (D.inGroup) {
|
||||
await groupRequest
|
||||
.getGroupMember({
|
||||
.getCachedGroupMember({
|
||||
groupId: D.id,
|
||||
userId: currentUser.value.id
|
||||
})
|
||||
@@ -1744,7 +1743,7 @@
|
||||
D.memberSearch = '';
|
||||
isGroupMembersLoading.value = true;
|
||||
await groupRequest
|
||||
.getGroupMembers(params)
|
||||
.getCachedGroupMembers(params)
|
||||
.finally(() => {
|
||||
isGroupMembersLoading.value = false;
|
||||
})
|
||||
@@ -1784,10 +1783,11 @@
|
||||
updateGroupDialogData({ ...groupDialog.value, galleries: {} });
|
||||
groupDialogGalleryCurrentName.value = '0';
|
||||
isGroupGalleryLoading.value = true;
|
||||
for (let i = 0; i < groupDialog.value.ref.galleries.length; i++) {
|
||||
const gallery = groupDialog.value.ref.galleries[i];
|
||||
await getGroupGallery(groupDialog.value.id, gallery.id);
|
||||
}
|
||||
const groupId = groupDialog.value.id;
|
||||
const tasks = (groupDialog.value.ref.galleries || []).map((gallery) =>
|
||||
getGroupGallery(groupId, gallery.id)
|
||||
);
|
||||
await Promise.allSettled(tasks);
|
||||
isGroupGalleryLoading.value = false;
|
||||
}
|
||||
|
||||
@@ -1801,7 +1801,7 @@
|
||||
};
|
||||
const count = 50; // 5000 max
|
||||
for (let i = 0; i < count; i++) {
|
||||
const args = await groupRequest.getGroupGallery(params);
|
||||
const args = await groupRequest.getCachedGroupGallery(params);
|
||||
if (args) {
|
||||
for (const json of args.json) {
|
||||
if (groupDialog.value.id === json.groupId) {
|
||||
@@ -1850,7 +1850,7 @@
|
||||
|
||||
async function setGroupMemberSortOrder(sortOrder) {
|
||||
const D = groupDialog.value;
|
||||
if (D.memberSortOrder.value === sortOrder) {
|
||||
if (D.memberSortOrder?.value === sortOrder?.value) {
|
||||
return;
|
||||
}
|
||||
D.memberSortOrder = sortOrder;
|
||||
|
||||
@@ -1318,7 +1318,7 @@
|
||||
userRequest,
|
||||
worldRequest
|
||||
} from '../../../api';
|
||||
import { processBulk, request } from '../../../service/request';
|
||||
import { processBulk } from '../../../service/request';
|
||||
import { userDialogGroupSortingOptions, userDialogMutualFriendSortingOptions } from '../../../shared/constants';
|
||||
import { userDialogWorldOrderOptions, userDialogWorldSortingOptions } from '../../../shared/constants/';
|
||||
import { database } from '../../../service/database';
|
||||
@@ -1429,6 +1429,8 @@
|
||||
const userDialogLastFavoriteWorld = ref('');
|
||||
|
||||
const favoriteWorldsTab = ref('0');
|
||||
const userDialogWorldsRequestId = ref(0);
|
||||
const userDialogFavoriteWorldsRequestId = ref(0);
|
||||
|
||||
const sendInviteDialogVisible = ref(false);
|
||||
const sendInviteDialog = ref({
|
||||
@@ -2388,6 +2390,7 @@
|
||||
if (D.isWorldsLoading) {
|
||||
return;
|
||||
}
|
||||
const requestId = ++userDialogWorldsRequestId.value;
|
||||
D.isWorldsLoading = true;
|
||||
const params = {
|
||||
n: 50,
|
||||
@@ -2402,30 +2405,40 @@
|
||||
params.user = 'me';
|
||||
params.releaseStatus = 'all';
|
||||
}
|
||||
const map = new Map();
|
||||
for (const ref of cachedWorlds.values()) {
|
||||
if (ref.authorId === D.id && (ref.authorId === currentUser.value.id || ref.releaseStatus === 'public')) {
|
||||
cachedWorlds.delete(ref.id);
|
||||
}
|
||||
}
|
||||
processBulk({
|
||||
fn: worldRequest.getWorlds,
|
||||
N: -1,
|
||||
params,
|
||||
handle: (args) => {
|
||||
for (const json of args.json) {
|
||||
const $ref = cachedWorlds.get(json.id);
|
||||
if (typeof $ref !== 'undefined') {
|
||||
map.set($ref.id, $ref);
|
||||
const worlds = [];
|
||||
const worldIds = new Set();
|
||||
(async () => {
|
||||
try {
|
||||
let offset = 0;
|
||||
while (true) {
|
||||
const args = await worldRequest.getCachedWorlds({
|
||||
...params,
|
||||
offset
|
||||
});
|
||||
if (requestId !== userDialogWorldsRequestId.value || D.id !== params.userId) {
|
||||
return;
|
||||
}
|
||||
for (const world of args.json) {
|
||||
if (!worldIds.has(world.id)) {
|
||||
worldIds.add(world.id);
|
||||
worlds.push(world);
|
||||
}
|
||||
}
|
||||
if (args.json.length < params.n) {
|
||||
break;
|
||||
}
|
||||
offset += params.n;
|
||||
}
|
||||
},
|
||||
done: () => {
|
||||
if (D.id === params.userId) {
|
||||
setUserDialogWorlds(D.id);
|
||||
if (requestId === userDialogWorldsRequestId.value && D.id === params.userId) {
|
||||
userDialog.value.worlds = worlds;
|
||||
}
|
||||
} finally {
|
||||
if (requestId === userDialogWorldsRequestId.value) {
|
||||
D.isWorldsLoading = false;
|
||||
}
|
||||
D.isWorldsLoading = false;
|
||||
}
|
||||
})().catch((err) => {
|
||||
console.error('refreshUserDialogWorlds failed', err);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2434,25 +2447,28 @@
|
||||
* @param userId
|
||||
*/
|
||||
async function getUserFavoriteWorlds(userId) {
|
||||
const requestId = ++userDialogFavoriteWorldsRequestId.value;
|
||||
userDialog.value.isFavoriteWorldsLoading = true;
|
||||
favoriteWorldsTab.value = '0';
|
||||
userDialog.value.userFavoriteWorlds = [];
|
||||
const worldLists = [];
|
||||
let params = {
|
||||
const groupArgs = await favoriteRequest.getCachedFavoriteGroups({
|
||||
ownerId: userId,
|
||||
n: 100,
|
||||
offset: 0
|
||||
};
|
||||
const json = await request('favorite/groups', {
|
||||
method: 'GET',
|
||||
params
|
||||
});
|
||||
for (let i = 0; i < json.length; ++i) {
|
||||
const list = json[i];
|
||||
if (list.type !== 'world') {
|
||||
continue;
|
||||
if (requestId !== userDialogFavoriteWorldsRequestId.value || userDialog.value.id !== userId) {
|
||||
if (requestId === userDialogFavoriteWorldsRequestId.value) {
|
||||
userDialog.value.isFavoriteWorldsLoading = false;
|
||||
}
|
||||
params = {
|
||||
return;
|
||||
}
|
||||
const worldGroups = groupArgs.json.filter((list) => list.type === 'world');
|
||||
const tasks = worldGroups.map(async (list) => {
|
||||
if (list.type !== 'world') {
|
||||
return null;
|
||||
}
|
||||
const params = {
|
||||
ownerId: userId,
|
||||
n: 100,
|
||||
offset: 0,
|
||||
@@ -2460,15 +2476,26 @@
|
||||
tag: list.name
|
||||
};
|
||||
try {
|
||||
const args = await favoriteRequest.getFavoriteWorlds(params);
|
||||
const args = await favoriteRequest.getCachedFavoriteWorlds(params);
|
||||
handleFavoriteWorldList(args);
|
||||
worldLists.push([list.displayName, list.visibility, args.json]);
|
||||
return [list.displayName, list.visibility, args.json];
|
||||
} catch (err) {
|
||||
console.error('getUserFavoriteWorlds', err);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
const results = await Promise.all(tasks);
|
||||
for (const result of results) {
|
||||
if (result) {
|
||||
worldLists.push(result);
|
||||
}
|
||||
}
|
||||
userDialog.value.userFavoriteWorlds = worldLists;
|
||||
userDialog.value.isFavoriteWorldsLoading = false;
|
||||
if (requestId === userDialogFavoriteWorldsRequestId.value) {
|
||||
if (userDialog.value.id === userId) {
|
||||
userDialog.value.userFavoriteWorlds = worldLists;
|
||||
}
|
||||
userDialog.value.isFavoriteWorldsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1212,8 +1212,7 @@
|
||||
break;
|
||||
case 'Refresh':
|
||||
const { tag, shortName } = worldDialog.value.$location;
|
||||
D.id = '';
|
||||
showWorldDialog(tag, shortName);
|
||||
showWorldDialog(tag, shortName, { forceRefresh: true });
|
||||
break;
|
||||
case 'New Instance':
|
||||
showNewInstanceDialog(D.$location.tag);
|
||||
|
||||
133
src/query/__tests__/entityCache.test.js
Normal file
133
src/query/__tests__/entityCache.test.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
_entityCacheInternals,
|
||||
fetchWithEntityPolicy,
|
||||
patchAndRefetchActiveQuery,
|
||||
patchQueryDataWithRecency
|
||||
} from '../entityCache';
|
||||
import { queryClient } from '../client';
|
||||
|
||||
describe('entity query cache helpers', () => {
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('reports cache hit for fresh data', async () => {
|
||||
const queryKey = ['user', 'usr_1'];
|
||||
let callCount = 0;
|
||||
|
||||
const policy = {
|
||||
staleTime: 20000,
|
||||
gcTime: 90000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
};
|
||||
|
||||
const queryFn = vi.fn(async () => {
|
||||
callCount++;
|
||||
return {
|
||||
json: { id: 'usr_1', updated_at: '2026-01-01T00:00:00.000Z' },
|
||||
params: { userId: 'usr_1' },
|
||||
ref: { id: 'usr_1', updated_at: '2026-01-01T00:00:00.000Z' }
|
||||
};
|
||||
});
|
||||
|
||||
const first = await fetchWithEntityPolicy({ queryKey, policy, queryFn });
|
||||
const second = await fetchWithEntityPolicy({ queryKey, policy, queryFn });
|
||||
|
||||
expect(first.cache).toBe(false);
|
||||
expect(second.cache).toBe(true);
|
||||
expect(callCount).toBe(1);
|
||||
});
|
||||
|
||||
test('always refetches when staleTime is zero (instance strategy)', async () => {
|
||||
const queryKey = ['instance', 'wrld_1', '12345'];
|
||||
let callCount = 0;
|
||||
|
||||
const policy = {
|
||||
staleTime: 0,
|
||||
gcTime: 10000,
|
||||
retry: 0,
|
||||
refetchOnWindowFocus: false
|
||||
};
|
||||
|
||||
const queryFn = vi.fn(async () => {
|
||||
callCount++;
|
||||
return {
|
||||
json: {
|
||||
id: 'wrld_1:12345',
|
||||
$fetchedAt: new Date().toJSON()
|
||||
},
|
||||
params: { worldId: 'wrld_1', instanceId: '12345' },
|
||||
ref: {
|
||||
id: 'wrld_1:12345',
|
||||
$fetchedAt: new Date().toJSON()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
await fetchWithEntityPolicy({ queryKey, policy, queryFn });
|
||||
await fetchWithEntityPolicy({ queryKey, policy, queryFn });
|
||||
|
||||
expect(callCount).toBe(2);
|
||||
});
|
||||
|
||||
test('does not overwrite newer data with older payload', () => {
|
||||
const queryKey = ['world', 'wrld_1'];
|
||||
|
||||
patchQueryDataWithRecency({
|
||||
queryKey,
|
||||
nextData: {
|
||||
ref: { id: 'wrld_1', updated_at: '2026-01-01T00:00:00.000Z' }
|
||||
}
|
||||
});
|
||||
|
||||
patchQueryDataWithRecency({
|
||||
queryKey,
|
||||
nextData: {
|
||||
ref: { id: 'wrld_1', updated_at: '2025-01-01T00:00:00.000Z' }
|
||||
}
|
||||
});
|
||||
|
||||
const cached = queryClient.getQueryData(queryKey);
|
||||
expect(cached.ref.updated_at).toBe('2026-01-01T00:00:00.000Z');
|
||||
});
|
||||
|
||||
test('patch and refetch invalidates only active queries for that key', async () => {
|
||||
const invalidateSpy = vi
|
||||
.spyOn(queryClient, 'invalidateQueries')
|
||||
.mockResolvedValue();
|
||||
|
||||
const queryKey = ['avatar', 'avtr_1'];
|
||||
await patchAndRefetchActiveQuery({
|
||||
queryKey,
|
||||
nextData: {
|
||||
ref: { id: 'avtr_1', updated_at: '2026-01-01T00:00:00.000Z' }
|
||||
}
|
||||
});
|
||||
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({
|
||||
queryKey,
|
||||
exact: true,
|
||||
refetchType: 'active'
|
||||
});
|
||||
});
|
||||
|
||||
test('internal recency guard prefers same-or-newer timestamps', () => {
|
||||
const newer = {
|
||||
ref: { id: 'usr_1', updated_at: '2026-02-01T00:00:00.000Z' }
|
||||
};
|
||||
const older = {
|
||||
ref: { id: 'usr_1', updated_at: '2026-01-01T00:00:00.000Z' }
|
||||
};
|
||||
|
||||
expect(_entityCacheInternals.shouldReplaceCurrent(older, newer)).toBe(
|
||||
true
|
||||
);
|
||||
expect(_entityCacheInternals.shouldReplaceCurrent(newer, older)).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
63
src/query/__tests__/keys.test.js
Normal file
63
src/query/__tests__/keys.test.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { queryKeys } from '../keys';
|
||||
|
||||
describe('query key shapes', () => {
|
||||
test('favorite world keys include owner and tag dimensions', () => {
|
||||
const a = queryKeys.favoriteWorlds({
|
||||
n: 100,
|
||||
offset: 0,
|
||||
ownerId: 'usr_1',
|
||||
userId: 'usr_1',
|
||||
tag: 'worlds1'
|
||||
});
|
||||
const b = queryKeys.favoriteWorlds({
|
||||
n: 100,
|
||||
offset: 0,
|
||||
ownerId: 'usr_2',
|
||||
userId: 'usr_2',
|
||||
tag: 'worlds1'
|
||||
});
|
||||
|
||||
expect(a).not.toEqual(b);
|
||||
});
|
||||
|
||||
test('world list keys include query option discriminator', () => {
|
||||
const base = {
|
||||
userId: 'usr_me',
|
||||
n: 50,
|
||||
offset: 0,
|
||||
sort: 'updated',
|
||||
order: 'descending',
|
||||
user: 'me',
|
||||
releaseStatus: 'all'
|
||||
};
|
||||
|
||||
const defaultKey = queryKeys.worldsByUser(base);
|
||||
const featuredKey = queryKeys.worldsByUser({
|
||||
...base,
|
||||
option: 'featured'
|
||||
});
|
||||
|
||||
expect(defaultKey).not.toEqual(featuredKey);
|
||||
});
|
||||
|
||||
test('group member list keys include sort and role dimensions', () => {
|
||||
const everyone = queryKeys.groupMembers({
|
||||
groupId: 'grp_1',
|
||||
n: 100,
|
||||
offset: 0,
|
||||
sort: 'joinedAt:desc',
|
||||
roleId: ''
|
||||
});
|
||||
const roleScoped = queryKeys.groupMembers({
|
||||
groupId: 'grp_1',
|
||||
n: 100,
|
||||
offset: 0,
|
||||
sort: 'joinedAt:desc',
|
||||
roleId: 'grol_1'
|
||||
});
|
||||
|
||||
expect(everyone).not.toEqual(roleScoped);
|
||||
});
|
||||
});
|
||||
137
src/query/__tests__/policies.test.js
Normal file
137
src/query/__tests__/policies.test.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import {
|
||||
entityQueryPolicies,
|
||||
getEntityQueryPolicy,
|
||||
toQueryOptions
|
||||
} from '../policies';
|
||||
|
||||
describe('query policy configuration', () => {
|
||||
test('matches the finalized cache strategy', () => {
|
||||
expect(entityQueryPolicies.user).toMatchObject({
|
||||
staleTime: 20000,
|
||||
gcTime: 90000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.avatar).toMatchObject({
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.world).toMatchObject({
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.group).toMatchObject({
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.groupCollection).toMatchObject({
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.worldCollection).toMatchObject({
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.instance).toMatchObject({
|
||||
staleTime: 0,
|
||||
gcTime: 10000,
|
||||
retry: 0,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.friendList).toMatchObject({
|
||||
staleTime: 20000,
|
||||
gcTime: 90000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.favoriteCollection).toMatchObject({
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.galleryCollection).toMatchObject({
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.inventoryCollection).toMatchObject({
|
||||
staleTime: 20000,
|
||||
gcTime: 120000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
|
||||
expect(entityQueryPolicies.fileObject).toMatchObject({
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
});
|
||||
|
||||
test('exposes entity policy lookup', () => {
|
||||
expect(getEntityQueryPolicy('user')).toBe(entityQueryPolicies.user);
|
||||
expect(getEntityQueryPolicy('avatar')).toBe(entityQueryPolicies.avatar);
|
||||
expect(getEntityQueryPolicy('world')).toBe(entityQueryPolicies.world);
|
||||
expect(getEntityQueryPolicy('group')).toBe(entityQueryPolicies.group);
|
||||
expect(getEntityQueryPolicy('groupCollection')).toBe(
|
||||
entityQueryPolicies.groupCollection
|
||||
);
|
||||
expect(getEntityQueryPolicy('worldCollection')).toBe(
|
||||
entityQueryPolicies.worldCollection
|
||||
);
|
||||
expect(getEntityQueryPolicy('instance')).toBe(
|
||||
entityQueryPolicies.instance
|
||||
);
|
||||
expect(getEntityQueryPolicy('friendList')).toBe(
|
||||
entityQueryPolicies.friendList
|
||||
);
|
||||
expect(getEntityQueryPolicy('favoriteCollection')).toBe(
|
||||
entityQueryPolicies.favoriteCollection
|
||||
);
|
||||
expect(getEntityQueryPolicy('galleryCollection')).toBe(
|
||||
entityQueryPolicies.galleryCollection
|
||||
);
|
||||
expect(getEntityQueryPolicy('inventoryCollection')).toBe(
|
||||
entityQueryPolicies.inventoryCollection
|
||||
);
|
||||
expect(getEntityQueryPolicy('fileObject')).toBe(
|
||||
entityQueryPolicies.fileObject
|
||||
);
|
||||
});
|
||||
|
||||
test('normalizes policy values to query options', () => {
|
||||
const options = toQueryOptions(entityQueryPolicies.group);
|
||||
|
||||
expect(options).toEqual({
|
||||
staleTime: 60000,
|
||||
gcTime: 300000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
});
|
||||
});
|
||||
});
|
||||
11
src/query/client.js
Normal file
11
src/query/client.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { QueryClient } from '@tanstack/vue-query';
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: true
|
||||
}
|
||||
}
|
||||
});
|
||||
230
src/query/entityCache.js
Normal file
230
src/query/entityCache.js
Normal file
@@ -0,0 +1,230 @@
|
||||
import { queryClient } from './client';
|
||||
import { queryKeys } from './keys';
|
||||
import { toQueryOptions } from './policies';
|
||||
|
||||
const RECENCY_FIELDS = [
|
||||
'updated_at',
|
||||
'updatedAt',
|
||||
'last_activity',
|
||||
'last_login',
|
||||
'memberCountSyncedAt',
|
||||
'$location_at',
|
||||
'$lastFetch',
|
||||
'$fetchedAt',
|
||||
'created_at',
|
||||
'createdAt'
|
||||
];
|
||||
|
||||
function getComparableEntity(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return null;
|
||||
}
|
||||
if (data.ref && typeof data.ref === 'object') {
|
||||
return data.ref;
|
||||
}
|
||||
if (data.json && typeof data.json === 'object' && !Array.isArray(data.json)) {
|
||||
return data.json;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function parseTimestamp(value) {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'string' && value !== '') {
|
||||
const parsed = Date.parse(value);
|
||||
if (!Number.isNaN(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getRecencyTimestamp(data) {
|
||||
const comparable = getComparableEntity(data);
|
||||
if (!comparable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const field of RECENCY_FIELDS) {
|
||||
const ts = parseTimestamp(comparable[field]);
|
||||
if (ts !== null) {
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function shouldReplaceCurrent(currentData, nextData) {
|
||||
if (typeof currentData === 'undefined') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const currentTs = getRecencyTimestamp(currentData);
|
||||
const nextTs = getRecencyTimestamp(nextData);
|
||||
|
||||
if (currentTs !== null && nextTs !== null) {
|
||||
return nextTs >= currentTs;
|
||||
}
|
||||
|
||||
if (currentTs !== null && nextTs === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{queryKey: unknown[], nextData: any}} options
|
||||
*/
|
||||
export function patchQueryDataWithRecency({ queryKey, nextData }) {
|
||||
queryClient.setQueryData(queryKey, (currentData) => {
|
||||
if (!shouldReplaceCurrent(currentData, nextData)) {
|
||||
return currentData;
|
||||
}
|
||||
return nextData;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{queryKey: unknown[], policy: {staleTime: number, gcTime: number, retry: number, refetchOnWindowFocus: boolean}, queryFn: () => Promise<any>}} options
|
||||
* @returns {Promise<{data: any, cache: boolean}>}
|
||||
*/
|
||||
export async function fetchWithEntityPolicy({ queryKey, policy, queryFn }) {
|
||||
const queryState = queryClient.getQueryState(queryKey);
|
||||
const isFresh =
|
||||
Boolean(queryState?.dataUpdatedAt) &&
|
||||
policy.staleTime > 0 &&
|
||||
Date.now() - queryState.dataUpdatedAt < policy.staleTime;
|
||||
|
||||
const data = await queryClient.fetchQuery({
|
||||
queryKey,
|
||||
queryFn,
|
||||
...toQueryOptions(policy)
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
cache: isFresh
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown[]} queryKey
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function refetchActiveEntityQuery(queryKey) {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey,
|
||||
exact: true,
|
||||
refetchType: 'active'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{queryKey: unknown[], nextData: any}} options
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function patchAndRefetchActiveQuery({ queryKey, nextData }) {
|
||||
patchQueryDataWithRecency({ queryKey, nextData });
|
||||
await refetchActiveEntityQuery(queryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} ref
|
||||
*/
|
||||
export function patchUserFromEvent(ref) {
|
||||
if (!ref?.id) return;
|
||||
patchQueryDataWithRecency({
|
||||
queryKey: queryKeys.user(ref.id),
|
||||
nextData: {
|
||||
cache: false,
|
||||
json: ref,
|
||||
params: { userId: ref.id },
|
||||
ref
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} ref
|
||||
*/
|
||||
export function patchAvatarFromEvent(ref) {
|
||||
if (!ref?.id) return;
|
||||
patchQueryDataWithRecency({
|
||||
queryKey: queryKeys.avatar(ref.id),
|
||||
nextData: {
|
||||
cache: false,
|
||||
json: ref,
|
||||
params: { avatarId: ref.id },
|
||||
ref
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} ref
|
||||
*/
|
||||
export function patchWorldFromEvent(ref) {
|
||||
if (!ref?.id) return;
|
||||
patchQueryDataWithRecency({
|
||||
queryKey: queryKeys.world(ref.id),
|
||||
nextData: {
|
||||
cache: false,
|
||||
json: ref,
|
||||
params: { worldId: ref.id },
|
||||
ref
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} ref
|
||||
*/
|
||||
export function patchGroupFromEvent(ref) {
|
||||
if (!ref?.id) return;
|
||||
|
||||
const nextData = {
|
||||
cache: false,
|
||||
json: ref,
|
||||
params: { groupId: ref.id },
|
||||
ref
|
||||
};
|
||||
|
||||
patchQueryDataWithRecency({
|
||||
queryKey: queryKeys.group(ref.id, false),
|
||||
nextData
|
||||
});
|
||||
|
||||
patchQueryDataWithRecency({
|
||||
queryKey: queryKeys.group(ref.id, true),
|
||||
nextData
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} ref
|
||||
*/
|
||||
export function patchInstanceFromEvent(ref) {
|
||||
if (!ref?.id) return;
|
||||
|
||||
const [worldId, instanceId] = String(ref.id).split(':');
|
||||
if (!worldId || !instanceId) return;
|
||||
|
||||
patchQueryDataWithRecency({
|
||||
queryKey: queryKeys.instance(worldId, instanceId),
|
||||
nextData: {
|
||||
cache: false,
|
||||
json: ref,
|
||||
params: { worldId, instanceId },
|
||||
ref
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const _entityCacheInternals = {
|
||||
getRecencyTimestamp,
|
||||
shouldReplaceCurrent
|
||||
};
|
||||
14
src/query/index.js
Normal file
14
src/query/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
export { queryClient } from './client';
|
||||
export { queryKeys } from './keys';
|
||||
export { entityQueryPolicies, getEntityQueryPolicy, toQueryOptions } from './policies';
|
||||
export {
|
||||
fetchWithEntityPolicy,
|
||||
patchAndRefetchActiveQuery,
|
||||
patchQueryDataWithRecency,
|
||||
patchUserFromEvent,
|
||||
patchAvatarFromEvent,
|
||||
patchWorldFromEvent,
|
||||
patchGroupFromEvent,
|
||||
patchInstanceFromEvent,
|
||||
refetchActiveEntityQuery
|
||||
} from './entityCache';
|
||||
149
src/query/keys.js
Normal file
149
src/query/keys.js
Normal file
@@ -0,0 +1,149 @@
|
||||
export const queryKeys = Object.freeze({
|
||||
user: (userId) => ['user', userId],
|
||||
avatar: (avatarId) => ['avatar', avatarId],
|
||||
world: (worldId) => ['world', worldId],
|
||||
group: (groupId, includeRoles = false) => ['group', groupId, Boolean(includeRoles)],
|
||||
groupPosts: ({ groupId, n = 100, offset = 0 } = {}) => [
|
||||
'group',
|
||||
groupId,
|
||||
'posts',
|
||||
{
|
||||
n: Number(n),
|
||||
offset: Number(offset)
|
||||
}
|
||||
],
|
||||
groupMember: ({ groupId, userId } = {}) => ['group', groupId, 'member', userId],
|
||||
groupMembers: ({ groupId, n = 100, offset = 0, sort = '', roleId = '' } = {}) => [
|
||||
'group',
|
||||
groupId,
|
||||
'members',
|
||||
{
|
||||
n: Number(n),
|
||||
offset: Number(offset),
|
||||
sort: String(sort || ''),
|
||||
roleId: String(roleId || '')
|
||||
}
|
||||
],
|
||||
groupGallery: ({ groupId, galleryId, n = 100, offset = 0 } = {}) => [
|
||||
'group',
|
||||
groupId,
|
||||
'gallery',
|
||||
galleryId,
|
||||
{
|
||||
n: Number(n),
|
||||
offset: Number(offset)
|
||||
}
|
||||
],
|
||||
groupCalendar: (groupId) => ['group', groupId, 'calendar'],
|
||||
groupCalendarEvent: ({ groupId, eventId } = {}) => [
|
||||
'group',
|
||||
groupId,
|
||||
'calendarEvent',
|
||||
eventId
|
||||
],
|
||||
instance: (worldId, instanceId) => ['instance', worldId, instanceId],
|
||||
worldsByUser: ({
|
||||
userId,
|
||||
n = 50,
|
||||
offset = 0,
|
||||
sort = '',
|
||||
order = '',
|
||||
user = '',
|
||||
releaseStatus = '',
|
||||
option = ''
|
||||
} = {}) => [
|
||||
'worlds',
|
||||
'user',
|
||||
userId,
|
||||
{
|
||||
n: Number(n),
|
||||
offset: Number(offset),
|
||||
sort: String(sort || ''),
|
||||
order: String(order || ''),
|
||||
user: String(user || ''),
|
||||
releaseStatus: String(releaseStatus || ''),
|
||||
option: String(option || '')
|
||||
}
|
||||
],
|
||||
friends: ({ offline = false, n = 50, offset = 0 } = {}) => [
|
||||
'friends',
|
||||
{
|
||||
offline: Boolean(offline),
|
||||
n: Number(n),
|
||||
offset: Number(offset)
|
||||
}
|
||||
],
|
||||
favoriteLimits: () => ['favorite', 'limits'],
|
||||
favorites: ({ n = 300, offset = 0 } = {}) => [
|
||||
'favorite',
|
||||
'items',
|
||||
{
|
||||
n: Number(n),
|
||||
offset: Number(offset)
|
||||
}
|
||||
],
|
||||
favoriteGroups: ({ n = 50, offset = 0, type = '' } = {}) => [
|
||||
'favorite',
|
||||
'groups',
|
||||
{
|
||||
n: Number(n),
|
||||
offset: Number(offset),
|
||||
type: String(type || '')
|
||||
}
|
||||
],
|
||||
favoriteWorlds: ({ n = 300, offset = 0, ownerId = '', userId = '', tag = '' } = {}) => [
|
||||
'favorite',
|
||||
'worlds',
|
||||
{
|
||||
n: Number(n),
|
||||
offset: Number(offset),
|
||||
ownerId: String(ownerId || ''),
|
||||
userId: String(userId || ''),
|
||||
tag: String(tag || '')
|
||||
}
|
||||
],
|
||||
favoriteAvatars: ({ n = 300, offset = 0, tag = '', ownerId = '', userId = '' } = {}) => [
|
||||
'favorite',
|
||||
'avatars',
|
||||
{
|
||||
n: Number(n),
|
||||
offset: Number(offset),
|
||||
tag: String(tag || ''),
|
||||
ownerId: String(ownerId || ''),
|
||||
userId: String(userId || '')
|
||||
}
|
||||
],
|
||||
galleryFiles: ({ tag = '', n = 100 } = {}) => [
|
||||
'gallery',
|
||||
'files',
|
||||
{
|
||||
tag: String(tag || ''),
|
||||
n: Number(n)
|
||||
}
|
||||
],
|
||||
prints: ({ n = 100 } = {}) => [
|
||||
'gallery',
|
||||
'prints',
|
||||
{
|
||||
n: Number(n)
|
||||
}
|
||||
],
|
||||
print: (printId) => ['gallery', 'print', printId],
|
||||
inventoryItems: ({ n = 100, offset = 0, order = 'newest', types = '' } = {}) => [
|
||||
'inventory',
|
||||
'items',
|
||||
{
|
||||
n: Number(n),
|
||||
offset: Number(offset),
|
||||
order: String(order || 'newest'),
|
||||
types: String(types || '')
|
||||
}
|
||||
],
|
||||
userInventoryItem: ({ inventoryId, userId }) => [
|
||||
'inventory',
|
||||
'item',
|
||||
userId,
|
||||
inventoryId
|
||||
],
|
||||
file: (fileId) => ['file', fileId]
|
||||
});
|
||||
97
src/query/policies.js
Normal file
97
src/query/policies.js
Normal file
@@ -0,0 +1,97 @@
|
||||
const SECOND = 1000;
|
||||
|
||||
export const entityQueryPolicies = Object.freeze({
|
||||
user: Object.freeze({
|
||||
staleTime: 20 * SECOND,
|
||||
gcTime: 90 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
avatar: Object.freeze({
|
||||
staleTime: 60 * SECOND,
|
||||
gcTime: 300 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
world: Object.freeze({
|
||||
staleTime: 60 * SECOND,
|
||||
gcTime: 300 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
group: Object.freeze({
|
||||
staleTime: 60 * SECOND,
|
||||
gcTime: 300 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
groupCollection: Object.freeze({
|
||||
staleTime: 60 * SECOND,
|
||||
gcTime: 300 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
worldCollection: Object.freeze({
|
||||
staleTime: 60 * SECOND,
|
||||
gcTime: 300 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
instance: Object.freeze({
|
||||
staleTime: 0,
|
||||
gcTime: 10 * SECOND,
|
||||
retry: 0,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
friendList: Object.freeze({
|
||||
staleTime: 20 * SECOND,
|
||||
gcTime: 90 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
favoriteCollection: Object.freeze({
|
||||
staleTime: 60 * SECOND,
|
||||
gcTime: 300 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
galleryCollection: Object.freeze({
|
||||
staleTime: 60 * SECOND,
|
||||
gcTime: 300 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
inventoryCollection: Object.freeze({
|
||||
staleTime: 20 * SECOND,
|
||||
gcTime: 120 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
}),
|
||||
fileObject: Object.freeze({
|
||||
staleTime: 60 * SECOND,
|
||||
gcTime: 300 * SECOND,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {'user'|'avatar'|'world'|'group'|'groupCollection'|'worldCollection'|'instance'|'friendList'|'favoriteCollection'|'galleryCollection'|'inventoryCollection'|'fileObject'} entity
|
||||
* @returns {{staleTime: number, gcTime: number, retry: number, refetchOnWindowFocus: boolean}}
|
||||
*/
|
||||
export function getEntityQueryPolicy(entity) {
|
||||
return entityQueryPolicies[entity];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{staleTime: number, gcTime: number, retry: number, refetchOnWindowFocus: boolean}} policy
|
||||
* @returns {{staleTime: number, gcTime: number, retry: number, refetchOnWindowFocus: boolean}}
|
||||
*/
|
||||
export function toQueryOptions(policy) {
|
||||
return {
|
||||
staleTime: policy.staleTime,
|
||||
gcTime: policy.gcTime,
|
||||
retry: policy.retry,
|
||||
refetchOnWindowFocus: policy.refetchOnWindowFocus
|
||||
};
|
||||
}
|
||||
55
src/query/useEntityQueries.js
Normal file
55
src/query/useEntityQueries.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { useQuery } from '@tanstack/vue-query';
|
||||
|
||||
import { avatarRequest, groupRequest, instanceRequest, userRequest, worldRequest } from '../api';
|
||||
import { queryKeys } from './keys';
|
||||
import { entityQueryPolicies, toQueryOptions } from './policies';
|
||||
|
||||
export function useUserQuery(userId, options = {}) {
|
||||
return useQuery({
|
||||
...options,
|
||||
queryKey: queryKeys.user(userId),
|
||||
queryFn: () => userRequest.getUser({ userId }),
|
||||
enabled: Boolean(userId),
|
||||
...toQueryOptions(entityQueryPolicies.user)
|
||||
});
|
||||
}
|
||||
|
||||
export function useAvatarQuery(avatarId, options = {}) {
|
||||
return useQuery({
|
||||
...options,
|
||||
queryKey: queryKeys.avatar(avatarId),
|
||||
queryFn: () => avatarRequest.getAvatar({ avatarId }),
|
||||
enabled: Boolean(avatarId),
|
||||
...toQueryOptions(entityQueryPolicies.avatar)
|
||||
});
|
||||
}
|
||||
|
||||
export function useWorldQuery(worldId, options = {}) {
|
||||
return useQuery({
|
||||
...options,
|
||||
queryKey: queryKeys.world(worldId),
|
||||
queryFn: () => worldRequest.getWorld({ worldId }),
|
||||
enabled: Boolean(worldId),
|
||||
...toQueryOptions(entityQueryPolicies.world)
|
||||
});
|
||||
}
|
||||
|
||||
export function useGroupQuery(groupId, includeRoles = false, options = {}) {
|
||||
return useQuery({
|
||||
...options,
|
||||
queryKey: queryKeys.group(groupId, includeRoles),
|
||||
queryFn: () => groupRequest.getGroup({ groupId, includeRoles }),
|
||||
enabled: Boolean(groupId),
|
||||
...toQueryOptions(entityQueryPolicies.group)
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstanceQuery(worldId, instanceId, options = {}) {
|
||||
return useQuery({
|
||||
...options,
|
||||
queryKey: queryKeys.instance(worldId, instanceId),
|
||||
queryFn: () => instanceRequest.getInstance({ worldId, instanceId }),
|
||||
enabled: Boolean(worldId && instanceId),
|
||||
...toQueryOptions(entityQueryPolicies.instance)
|
||||
});
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
storeAvatarImage
|
||||
} from '../shared/utils';
|
||||
import { avatarRequest, miscRequest } from '../api';
|
||||
import { patchAvatarFromEvent } from '../query';
|
||||
import { AppDebug } from '../service/appConfig';
|
||||
import { database } from '../service/database';
|
||||
import { processBulk } from '../service/request';
|
||||
@@ -170,6 +171,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
// update db cache
|
||||
database.addAvatarToCache(avatarRef);
|
||||
}
|
||||
patchAvatarFromEvent(ref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
@@ -178,14 +180,15 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
* @param {string} avatarId
|
||||
* @returns
|
||||
*/
|
||||
function showAvatarDialog(avatarId) {
|
||||
function showAvatarDialog(avatarId, options = {}) {
|
||||
const D = avatarDialog.value;
|
||||
const forceRefresh = Boolean(options?.forceRefresh);
|
||||
const isMainDialogOpen = uiStore.openDialog({
|
||||
type: 'avatar',
|
||||
id: avatarId
|
||||
});
|
||||
D.visible = true;
|
||||
if (isMainDialogOpen && D.id === avatarId) {
|
||||
if (isMainDialogOpen && D.id === avatarId && !forceRefresh) {
|
||||
uiStore.setDialogCrumbLabel('avatar', D.id, D.ref?.name || D.id);
|
||||
nextTick(() => (D.loading = false));
|
||||
return;
|
||||
@@ -217,8 +220,10 @@ export const useAvatarStore = defineStore('Avatar', () => {
|
||||
uiStore.setDialogCrumbLabel('avatar', D.id, D.ref?.name || D.id);
|
||||
nextTick(() => (D.loading = false));
|
||||
}
|
||||
avatarRequest
|
||||
.getAvatar({ avatarId })
|
||||
const loadAvatarRequest = forceRefresh
|
||||
? avatarRequest.getAvatar({ avatarId })
|
||||
: avatarRequest.getCachedAvatar({ avatarId });
|
||||
loadAvatarRequest
|
||||
.then((args) => {
|
||||
const ref = applyAvatar(args.json);
|
||||
D.ref = ref;
|
||||
|
||||
@@ -542,7 +542,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
isFavoriteGroupLoading.value = true;
|
||||
processBulk({
|
||||
fn: favoriteRequest.getFavoriteGroups,
|
||||
fn: favoriteRequest.getCachedFavoriteGroups,
|
||||
N: -1,
|
||||
params: {
|
||||
n: 50,
|
||||
@@ -707,7 +707,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
isFavoriteLoading.value = true;
|
||||
try {
|
||||
const args = await favoriteRequest.getFavoriteLimits();
|
||||
const args = await favoriteRequest.getCachedFavoriteLimits();
|
||||
favoriteLimits.value = {
|
||||
...favoriteLimits.value,
|
||||
...args.json
|
||||
@@ -717,7 +717,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
}
|
||||
let newFavoriteSortOrder = [];
|
||||
processBulk({
|
||||
fn: favoriteRequest.getFavorites,
|
||||
fn: favoriteRequest.getCachedFavorites,
|
||||
N: -1,
|
||||
params: {
|
||||
n: 300,
|
||||
@@ -839,7 +839,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
offset: 0,
|
||||
tag
|
||||
};
|
||||
const args = await favoriteRequest.getFavoriteAvatars(params);
|
||||
const args = await favoriteRequest.getCachedFavoriteAvatars(params);
|
||||
handleFavoriteAvatarList(args);
|
||||
}
|
||||
|
||||
@@ -848,8 +848,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
*/
|
||||
function refreshFavoriteItems() {
|
||||
const types = {
|
||||
world: [0, favoriteRequest.getFavoriteWorlds],
|
||||
avatar: [0, favoriteRequest.getFavoriteAvatars]
|
||||
world: [0, favoriteRequest.getCachedFavoriteWorlds],
|
||||
avatar: [0, favoriteRequest.getCachedFavoriteAvatars]
|
||||
};
|
||||
const tags = [];
|
||||
for (const ref of cachedFavorites.values()) {
|
||||
|
||||
@@ -777,7 +777,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
async function fetchPage(offset) {
|
||||
const result = await executeWithBackoff(
|
||||
async () => {
|
||||
const { json } = await friendRequest.getFriends({
|
||||
const { json } = await friendRequest.getCachedFriends({
|
||||
...args,
|
||||
n: PAGE_SIZE,
|
||||
offset
|
||||
|
||||
@@ -149,7 +149,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
tag: 'gallery'
|
||||
};
|
||||
vrcPlusIconRequest
|
||||
.getFileList(params)
|
||||
.getCachedFileList(params)
|
||||
.then((args) => handleFilesList(args))
|
||||
.catch((error) => {
|
||||
console.error('Error fetching gallery files:', error);
|
||||
@@ -166,7 +166,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
tag: 'icon'
|
||||
};
|
||||
vrcPlusIconRequest
|
||||
.getFileList(params)
|
||||
.getCachedFileList(params)
|
||||
.then((args) => handleFilesList(args))
|
||||
.catch((error) => {
|
||||
console.error('Error fetching VRC Plus icons:', error);
|
||||
@@ -208,7 +208,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
tag: 'sticker'
|
||||
};
|
||||
vrcPlusIconRequest
|
||||
.getFileList(params)
|
||||
.getCachedFileList(params)
|
||||
.then((args) => handleFilesList(args))
|
||||
.catch((error) => {
|
||||
console.error('Error fetching stickers:', error);
|
||||
@@ -232,7 +232,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
if (instanceStickersCache.value.length > 100) {
|
||||
instanceStickersCache.value.shift();
|
||||
}
|
||||
const args = await inventoryRequest.getUserInventoryItem({
|
||||
const args = await inventoryRequest.getCachedUserInventoryItem({
|
||||
inventoryId,
|
||||
userId
|
||||
});
|
||||
@@ -269,7 +269,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
n: 100
|
||||
};
|
||||
try {
|
||||
const args = await vrcPlusImageRequest.getPrints(params);
|
||||
const args = await vrcPlusImageRequest.getCachedPrints(params);
|
||||
args.json.sort((a, b) => {
|
||||
return (
|
||||
new Date(b.timestamp).getTime() -
|
||||
@@ -306,7 +306,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
}
|
||||
|
||||
async function trySavePrintToFile(printId) {
|
||||
const args = await vrcPlusImageRequest.getPrint({ printId });
|
||||
const args = await vrcPlusImageRequest.getCachedPrint({ printId });
|
||||
const imageUrl = args.json?.files?.image;
|
||||
if (!imageUrl) {
|
||||
console.error('Print image URL is missing', args);
|
||||
@@ -357,7 +357,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
tag: 'emoji'
|
||||
};
|
||||
vrcPlusIconRequest
|
||||
.getFileList(params)
|
||||
.getCachedFileList(params)
|
||||
.then((args) => handleFilesList(args))
|
||||
.catch((error) => {
|
||||
console.error('Error fetching emojis:', error);
|
||||
@@ -379,7 +379,9 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
try {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
params.offset = i * params.n;
|
||||
const args = await inventoryRequest.getInventoryItems(params);
|
||||
const args = await inventoryRequest.getCachedInventoryItems(
|
||||
params
|
||||
);
|
||||
for (const item of args.json.data) {
|
||||
advancedSettingsStore.currentUserInventory.set(
|
||||
item.id,
|
||||
@@ -476,7 +478,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
}
|
||||
|
||||
async function trySaveEmojiToFile(inventoryId, userId) {
|
||||
const args = await inventoryRequest.getUserInventoryItem({
|
||||
const args = await inventoryRequest.getCachedUserInventoryItem({
|
||||
inventoryId,
|
||||
userId
|
||||
});
|
||||
@@ -548,7 +550,7 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
||||
return;
|
||||
}
|
||||
miscReq
|
||||
.getFile({ fileId })
|
||||
.getCachedFile({ fileId })
|
||||
.then((args) => {
|
||||
cachedEmoji.set(fileId, args.json);
|
||||
resolve(args.json);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
userRequest,
|
||||
worldRequest
|
||||
} from '../api';
|
||||
import { patchGroupFromEvent } from '../query';
|
||||
import {
|
||||
convertFileUrlToImageUrl,
|
||||
hasGroupPermission,
|
||||
@@ -127,17 +128,18 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
{ flush: 'sync' }
|
||||
);
|
||||
|
||||
function showGroupDialog(groupId) {
|
||||
function showGroupDialog(groupId, options = {}) {
|
||||
if (!groupId) {
|
||||
return;
|
||||
}
|
||||
const forceRefresh = Boolean(options?.forceRefresh);
|
||||
const isMainDialogOpen = uiStore.openDialog({
|
||||
type: 'group',
|
||||
id: groupId
|
||||
});
|
||||
const D = groupDialog.value;
|
||||
D.visible = true;
|
||||
if (isMainDialogOpen && D.id === groupId) {
|
||||
if (isMainDialogOpen && D.id === groupId && !forceRefresh) {
|
||||
uiStore.setDialogCrumbLabel('group', D.id, D.ref?.name || D.id);
|
||||
instanceStore.applyGroupDialogInstances();
|
||||
D.loading = false;
|
||||
@@ -159,10 +161,15 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
D.members = [];
|
||||
D.memberFilter = groupDialogFilterOptions.everyone;
|
||||
D.calendar = [];
|
||||
groupRequest
|
||||
.getCachedGroup({
|
||||
groupId
|
||||
})
|
||||
const loadGroupRequest = forceRefresh
|
||||
? groupRequest.getGroup({
|
||||
groupId,
|
||||
includeRoles: false
|
||||
})
|
||||
: groupRequest.getCachedGroup({
|
||||
groupId
|
||||
});
|
||||
loadGroupRequest
|
||||
.catch((err) => {
|
||||
D.loading = false;
|
||||
D.id = null;
|
||||
@@ -172,29 +179,27 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
if (groupId === args.ref.id) {
|
||||
D.ref = args.ref;
|
||||
const ref = args.ref || applyGroup(args.json);
|
||||
if (groupId === ref.id) {
|
||||
D.ref = ref;
|
||||
uiStore.setDialogCrumbLabel(
|
||||
'group',
|
||||
D.id,
|
||||
D.ref?.name || D.id
|
||||
);
|
||||
D.inGroup = args.ref.membershipStatus === 'member';
|
||||
D.ownerDisplayName = args.ref.ownerId;
|
||||
D.inGroup = ref.membershipStatus === 'member';
|
||||
D.ownerDisplayName = ref.ownerId;
|
||||
D.visible = true;
|
||||
D.loading = false;
|
||||
if (args.cache) {
|
||||
groupRequest.getGroup(args.params);
|
||||
}
|
||||
userRequest
|
||||
.getCachedUser({
|
||||
userId: args.ref.ownerId
|
||||
userId: ref.ownerId
|
||||
})
|
||||
.then((args1) => {
|
||||
D.ownerDisplayName = args1.ref.displayName;
|
||||
});
|
||||
database.getLastGroupVisit(D.ref.name).then((r) => {
|
||||
if (D.id === args.ref.id) {
|
||||
if (D.id === ref.id) {
|
||||
D.lastVisit = r.created_at;
|
||||
}
|
||||
});
|
||||
@@ -400,33 +405,37 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
*/
|
||||
async function getAllGroupPosts(params) {
|
||||
const n = 100;
|
||||
let posts = [];
|
||||
const posts = [];
|
||||
let offset = 0;
|
||||
let total = 0;
|
||||
const args = await groupRequest.getGroupPosts({
|
||||
groupId: params.groupId,
|
||||
n,
|
||||
offset
|
||||
});
|
||||
let total = Infinity;
|
||||
let pages = 0;
|
||||
do {
|
||||
posts = posts.concat(args.json.posts);
|
||||
total = args.json.total;
|
||||
const args = await groupRequest.getCachedGroupPosts({
|
||||
groupId: params.groupId,
|
||||
n,
|
||||
offset
|
||||
});
|
||||
const pagePosts = args.json?.posts ?? [];
|
||||
total = Number(args.json?.total ?? pagePosts.length);
|
||||
posts.push(...pagePosts);
|
||||
offset += n;
|
||||
} while (offset < total);
|
||||
pages += 1;
|
||||
if (pagePosts.length === 0) {
|
||||
break;
|
||||
}
|
||||
} while (offset < total && pages < 50);
|
||||
const returnArgs = {
|
||||
posts,
|
||||
params
|
||||
};
|
||||
const D = groupDialog.value;
|
||||
if (D.id === args.params.groupId) {
|
||||
for (const post of args.json.posts) {
|
||||
if (D.id === params.groupId) {
|
||||
for (const post of posts) {
|
||||
post.title = replaceBioSymbols(post.title);
|
||||
post.text = replaceBioSymbols(post.text);
|
||||
}
|
||||
if (args.json.posts.length > 0) {
|
||||
D.announcement = args.json.posts[0];
|
||||
}
|
||||
D.posts = args.json.posts;
|
||||
D.announcement = posts[0] ?? {};
|
||||
D.posts = posts;
|
||||
updateGroupPostSearch();
|
||||
}
|
||||
|
||||
@@ -437,7 +446,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
const D = groupDialog.value;
|
||||
D.isGetGroupDialogGroupLoading = false;
|
||||
return groupRequest
|
||||
.getGroup({ groupId, includeRoles: true })
|
||||
.getCachedGroup({ groupId, includeRoles: true })
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
})
|
||||
@@ -447,6 +456,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
D.loading = false;
|
||||
D.ref = ref;
|
||||
D.inGroup = ref.membershipStatus === 'member';
|
||||
D.memberRoles = [];
|
||||
for (const role of ref.roles) {
|
||||
if (
|
||||
D.ref &&
|
||||
@@ -487,14 +497,14 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
groupRequest.getGroupCalendar(groupId).then((args) => {
|
||||
groupRequest.getCachedGroupCalendar(groupId).then((args) => {
|
||||
if (groupDialog.value.id === args.params.groupId) {
|
||||
D.calendar = args.json.results;
|
||||
for (const event of D.calendar) {
|
||||
applyGroupEvent(event);
|
||||
// fetch again for isFollowing
|
||||
groupRequest
|
||||
.getGroupCalendarEvent({
|
||||
.getCachedGroupCalendarEvent({
|
||||
groupId,
|
||||
eventId: event.id
|
||||
})
|
||||
@@ -782,6 +792,7 @@ export const useGroupStore = defineStore('Group', () => {
|
||||
D.inGroup = ref.membershipStatus === 'member';
|
||||
D.ref = ref;
|
||||
}
|
||||
patchGroupFromEvent(ref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
userRequest,
|
||||
worldRequest
|
||||
} from '../api';
|
||||
import { patchInstanceFromEvent } from '../query';
|
||||
import {
|
||||
accessTypeLocaleKeyMap,
|
||||
instanceContentSettings
|
||||
@@ -573,6 +574,7 @@ export const useInstanceStore = defineStore('Instance', () => {
|
||||
}
|
||||
}
|
||||
lastInstanceApplied.value = ref.id;
|
||||
patchInstanceFromEvent(ref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
instanceRequest,
|
||||
userRequest
|
||||
} from '../api';
|
||||
import { patchUserFromEvent } from '../query';
|
||||
import { processBulk, request } from '../service/request';
|
||||
import { AppDebug } from '../service/appConfig';
|
||||
import { database } from '../service/database';
|
||||
@@ -764,6 +765,7 @@ export const useUserStore = defineStore('User', () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
patchUserFromEvent(ref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
@@ -920,9 +922,6 @@ export const useUserStore = defineStore('User', () => {
|
||||
if (locationStore.lastLocation.playerList.has(D.ref.id)) {
|
||||
inCurrentWorld = true;
|
||||
}
|
||||
if (args.cache) {
|
||||
userRequest.getUser(args.params);
|
||||
}
|
||||
if (userId !== currentUser.value.id) {
|
||||
database
|
||||
.getUserStats(D.ref, inCurrentWorld)
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
replaceBioSymbols
|
||||
} from '../shared/utils';
|
||||
import { instanceRequest, miscRequest, worldRequest } from '../api';
|
||||
import { patchWorldFromEvent } from '../query';
|
||||
import { database } from '../service/database';
|
||||
import { processBulk } from '../service/request';
|
||||
import { useFavoriteStore } from './favorite';
|
||||
@@ -76,8 +77,9 @@ export const useWorldStore = defineStore('World', () => {
|
||||
* @param {string} tag
|
||||
* @param {string} shortName
|
||||
*/
|
||||
function showWorldDialog(tag, shortName = null) {
|
||||
function showWorldDialog(tag, shortName = null, options = {}) {
|
||||
const D = worldDialog;
|
||||
const forceRefresh = Boolean(options?.forceRefresh);
|
||||
const L = parseLocation(tag);
|
||||
if (L.worldId === '') {
|
||||
return;
|
||||
@@ -89,7 +91,7 @@ export const useWorldStore = defineStore('World', () => {
|
||||
shortName
|
||||
});
|
||||
D.visible = true;
|
||||
if (isMainDialogOpen && D.id === L.worldId) {
|
||||
if (isMainDialogOpen && D.id === L.worldId && !forceRefresh) {
|
||||
uiStore.setDialogCrumbLabel('world', D.id, D.ref?.name || D.id);
|
||||
instanceStore.applyWorldDialogInstances();
|
||||
nextTick(() => (D.loading = false));
|
||||
@@ -141,10 +143,14 @@ export const useWorldStore = defineStore('World', () => {
|
||||
D.timeSpent = ref.timeSpent;
|
||||
}
|
||||
});
|
||||
worldRequest
|
||||
.getCachedWorld({
|
||||
worldId: L.worldId
|
||||
})
|
||||
const loadWorldRequest = forceRefresh
|
||||
? worldRequest.getWorld({
|
||||
worldId: L.worldId
|
||||
})
|
||||
: worldRequest.getCachedWorld({
|
||||
worldId: L.worldId
|
||||
});
|
||||
loadWorldRequest
|
||||
.catch((err) => {
|
||||
nextTick(() => (D.loading = false));
|
||||
D.id = null;
|
||||
@@ -199,13 +205,6 @@ export const useWorldStore = defineStore('World', () => {
|
||||
}
|
||||
});
|
||||
|
||||
if (args.cache) {
|
||||
worldRequest.getWorld(args.params).then((args1) => {
|
||||
if (D.id === args1.ref.id) {
|
||||
updateVRChatWorldCache();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -343,6 +342,7 @@ export const useWorldStore = defineStore('World', () => {
|
||||
// update db cache
|
||||
database.addWorldToCache(ref);
|
||||
}
|
||||
patchWorldFromEvent(ref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user