From 7864fbe6a7ae99e5218e941c2c23949fe1384eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Sat, 6 Jul 2024 09:31:39 +0200 Subject: [PATCH 01/12] Update gh-pages --- package-lock.json | 421 +++++----------------------------------------- package.json | 2 +- 2 files changed, 42 insertions(+), 381 deletions(-) diff --git a/package-lock.json b/package-lock.json index d1afb67..872c8c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "query-response-spring-amqp", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "devDependencies": { - "gh-pages": "^5.0.0", + "gh-pages": "^6.0.0", "lodash": "^4.17.21" } }, @@ -31,9 +31,9 @@ } }, "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, "node_modules/balanced-match": { @@ -53,10 +53,13 @@ } }, "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } }, "node_modules/commondir": { "version": "1.0.1", @@ -142,17 +145,17 @@ } }, "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=14.14" } }, "node_modules/fs.realpath": { @@ -162,17 +165,17 @@ "dev": true }, "node_modules/gh-pages": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-5.0.0.tgz", - "integrity": "sha512-Nqp1SjkPIB94Xw/3yYNTUL+G2dxlhjvv1zeN/4kMC1jfViTEqhtVz/Ba1zSXHuvXCN9ADNS1dN4r5/J/nZWEQQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.1.1.tgz", + "integrity": "sha512-upnohfjBwN5hBP9w2dPE7HO5JJTHzSGMV1JrLrHvNuqmjoYHg6TBrCcnEoorjG/e0ejbuvnwyKMdTyM40PEByw==", "dev": true, "dependencies": { "async": "^3.2.4", - "commander": "^2.18.0", + "commander": "^11.0.0", "email-addresses": "^5.0.0", "filenamify": "^4.3.0", "find-cache-dir": "^3.3.1", - "fs-extra": "^8.1.0", + "fs-extra": "^11.1.1", "globby": "^6.1.0" }, "bin": { @@ -187,6 +190,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -220,15 +224,16 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -242,10 +247,13 @@ "dev": true }, "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -410,9 +418,9 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -443,12 +451,12 @@ } }, "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { - "node": ">= 4.0.0" + "node": ">= 10.0.0" } }, "node_modules/wrappy": { @@ -457,352 +465,5 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true } - }, - "dependencies": { - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "email-addresses": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", - "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", - "dev": true - }, - "filenamify": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", - "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", - "dev": true, - "requires": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.1", - "trim-repeated": "^1.0.0" - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "gh-pages": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-5.0.0.tgz", - "integrity": "sha512-Nqp1SjkPIB94Xw/3yYNTUL+G2dxlhjvv1zeN/4kMC1jfViTEqhtVz/Ba1zSXHuvXCN9ADNS1dN4r5/J/nZWEQQ==", - "dev": true, - "requires": { - "async": "^3.2.4", - "commander": "^2.18.0", - "email-addresses": "^5.0.0", - "filenamify": "^4.3.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "^8.1.0", - "globby": "^6.1.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, - "trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - } } } diff --git a/package.json b/package.json index 8cb3a11..568dac0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "devDependencies": { - "gh-pages": "^5.0.0", + "gh-pages": "^6.0.0", "lodash": "^4.17.21" }, "scripts": { From 61b23ea7b5c36a60ae1a5aeb0c7f26693f0510fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Mon, 8 Jul 2024 21:00:06 +0200 Subject: [PATCH 02/12] Add vitepress --- package-lock.json | 1666 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 8 +- 2 files changed, 1671 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 872c8c8..74dc470 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,1241 @@ "": { "devDependencies": { "gh-pages": "^6.0.0", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "vitepress": "^1.2.3" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "dev": true, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", + "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", + "dev": true, + "dependencies": { + "@algolia/cache-common": "4.24.0" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", + "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==", + "dev": true + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", + "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", + "dev": true, + "dependencies": { + "@algolia/cache-common": "4.24.0" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", + "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", + "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dev": true, + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", + "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/logger-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", + "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==", + "dev": true + }, + "node_modules/@algolia/logger-console": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", + "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", + "dev": true, + "dependencies": { + "@algolia/logger-common": "4.24.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", + "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", + "dev": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dev": true, + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", + "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==", + "dev": true + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dev": true, + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", + "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", + "dev": true, + "dependencies": { + "@algolia/cache-common": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", + "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==", + "dev": true + }, + "node_modules/@docsearch/js": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz", + "integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==", + "dev": true, + "dependencies": { + "@docsearch/react": "3.6.0", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", + "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-core": "1.9.3", + "@algolia/autocomplete-preset-algolia": "1.9.3", + "@docsearch/css": "3.6.0", + "algoliasearch": "^4.19.1" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.10.1.tgz", + "integrity": "sha512-qdiJS5a/QGCff7VUFIqd0hDdWly9rDp8lhVmXVrS11aazX8LOTRLHAXkkEeONNsS43EcCd7gax9LLoOz4vlFQA==", + "dev": true + }, + "node_modules/@shikijs/transformers": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.10.1.tgz", + "integrity": "sha512-0gLtcFyi6R6zcUkFajUEp1Qiv7lHBSFgOz4tQvS8nFsYCQSLI1/9pM+Me8jEIPXv7XLKAoUjw6InL+Sv+BHw/A==", + "dev": true, + "dependencies": { + "shiki": "1.10.1" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dev": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz", + "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.3.5.tgz", + "integrity": "sha512-BSdBBu5hOIv+gBJC9jzYMh5bC27FQwjWLSb8fVAniqlL9gvsqvK27xTgczMf+hgctlszMYQnRm3bpY/j8vhPqw==", + "dev": true, + "dependencies": { + "@vue/devtools-kit": "^7.3.5" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.5.tgz", + "integrity": "sha512-wwfi10gJ1HMtjzcd8aIOnzBHlIRqsYDgcDyrKvkeyc0Gbcoe7UrkXRVHZUOtcxxoplHA0PwpT6wFg0uUCmi8Ww==", + "dev": true, + "dependencies": { + "@vue/devtools-shared": "^7.3.5", + "birpc": "^0.2.17", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.5.tgz", + "integrity": "sha512-Rqii3VazmWTi67a86rYopi61n5Ved05EybJCwyrfoO9Ok3MaS/4yRFl706ouoISMlyrASJFEzM0/AiDA6w4f9A==", + "dev": true, + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dev": true, + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dev": true, + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.31.tgz", + "integrity": "sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==", + "dev": true + }, + "node_modules/@vueuse/core": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.0.tgz", + "integrity": "sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==", + "dev": true, + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.0", + "@vueuse/shared": "10.11.0", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "dev": true, + "hasInstallScript": true, + "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/@vueuse/integrations": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.11.0.tgz", + "integrity": "sha512-Pp6MtWEIr+NDOccWd8j59Kpjy5YDXogXI61Kb1JxvSfVBO8NzFQkmrKmSZz47i+ZqHnIzxaT38L358yDHTncZg==", + "dev": true, + "dependencies": { + "@vueuse/core": "10.11.0", + "@vueuse/shared": "10.11.0", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^4", + "drauu": "^0.3", + "focus-trap": "^7", + "fuse.js": "^6", + "idb-keyval": "^6", + "jwt-decode": "^3", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^6" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations/node_modules/vue-demi": { + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "dev": true, + "hasInstallScript": true, + "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/@vueuse/metadata": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.0.tgz", + "integrity": "sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.0.tgz", + "integrity": "sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==", + "dev": true, + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "dev": true, + "hasInstallScript": true, + "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/algoliasearch": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", + "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", + "dev": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-account": "4.24.0", + "@algolia/client-analytics": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-personalization": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/recommend": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/array-union": { @@ -42,6 +1276,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/birpc": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.17.tgz", + "integrity": "sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -73,12 +1316,83 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, "node_modules/email-addresses": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -88,6 +1402,12 @@ "node": ">=0.8.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/filename-reserved-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", @@ -144,6 +1464,15 @@ "node": ">=8" } }, + "node_modules/focus-trap": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", + "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", + "dev": true, + "dependencies": { + "tabbable": "^6.2.0" + } + }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -164,6 +1493,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/gh-pages": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.1.1.tgz", @@ -229,6 +1572,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -246,6 +1595,18 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -276,6 +1637,15 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -291,6 +1661,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -303,6 +1679,36 @@ "node": "*" } }, + "node_modules/minisearch": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", + "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", + "dev": true + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -375,6 +1781,18 @@ "node": ">=0.10.0" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -417,6 +1835,92 @@ "node": ">=8" } }, + "node_modules/postcss": { + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.22.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.1.tgz", + "integrity": "sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.14.0.tgz", + "integrity": "sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==", + "dev": true, + "peer": true + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -426,6 +1930,33 @@ "semver": "bin/semver.js" } }, + "node_modules/shiki": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.10.1.tgz", + "integrity": "sha512-uafV7WCgN4YYrccH6yxpnps6k38sSTlFRrwc4jycWmhWxJIm9dPrk+XkY1hZ2t0I7jmacMNb15Lf2fspa/Y3lg==", + "dev": true, + "dependencies": { + "@shikijs/core": "1.10.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", @@ -438,6 +1969,24 @@ "node": ">=0.10.0" } }, + "node_modules/superjson": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", + "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==", + "dev": true, + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true + }, "node_modules/trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", @@ -459,6 +2008,121 @@ "node": ">= 10.0.0" } }, + "node_modules/vite": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz", + "integrity": "sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.39", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.2.3.tgz", + "integrity": "sha512-GvEsrEeNLiDE1+fuwDAYJCYLNZDAna+EtnXlPajhv/MYeTjbNK6Bvyg6NoTdO1sbwuQJ0vuJR99bOlH53bo6lg==", + "dev": true, + "dependencies": { + "@docsearch/css": "^3.6.0", + "@docsearch/js": "^3.6.0", + "@shikijs/core": "^1.6.2", + "@shikijs/transformers": "^1.6.2", + "@types/markdown-it": "^14.1.1", + "@vitejs/plugin-vue": "^5.0.5", + "@vue/devtools-api": "^7.2.1", + "@vue/shared": "^3.4.27", + "@vueuse/core": "^10.10.0", + "@vueuse/integrations": "^10.10.0", + "focus-trap": "^7.5.4", + "mark.js": "8.11.1", + "minisearch": "^6.3.0", + "shiki": "^1.6.2", + "vite": "^5.2.12", + "vue": "^3.4.27" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 568dac0..598e599 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,13 @@ "private": true, "devDependencies": { "gh-pages": "^6.0.0", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "vitepress": "^1.2.3" }, "scripts": { - "deploy-docs": "gh-pages -d target/generated-docs" + "deploy-docs": "gh-pages -d target/generated-docs", + "docs:dev": "vitepress dev xdocs", + "docs:build": "vitepress build xdocs", + "docs:preview": "vitepress preview xdocs" } } From 427e8238c30ee27b76dbc9e05f60b47593f337c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Mon, 8 Jul 2024 21:00:16 +0200 Subject: [PATCH 03/12] Ignore vitepress cache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 49a0177..45d38d5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ target/ node_modules/ .factorypath +**/.vitepress/cache From 6d98ca33a80f4a050b6341721b33973c708d3b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Mon, 8 Jul 2024 21:00:54 +0200 Subject: [PATCH 04/12] Initial vitepress documentation --- xdocs/.vitepress/config.mjs | 46 ++++ xdocs/attributes.js | 25 ++ xdocs/guide/getting-started.md | 160 +++++++++++++ xdocs/guide/the-example-revisited.md | 150 ++++++++++++ xdocs/guide/what-is-query-response.md | 216 ++++++++++++++++++ xdocs/index.md | 68 ++++++ .../query-response-maturity-model.md | 64 ++++++ .../the-query-response-specification.md | 167 ++++++++++++++ 8 files changed, 896 insertions(+) create mode 100644 xdocs/.vitepress/config.mjs create mode 100644 xdocs/attributes.js create mode 100644 xdocs/guide/getting-started.md create mode 100644 xdocs/guide/the-example-revisited.md create mode 100644 xdocs/guide/what-is-query-response.md create mode 100644 xdocs/index.md create mode 100644 xdocs/reference/query-response-maturity-model.md create mode 100644 xdocs/reference/the-query-response-specification.md diff --git a/xdocs/.vitepress/config.mjs b/xdocs/.vitepress/config.mjs new file mode 100644 index 0000000..406eebd --- /dev/null +++ b/xdocs/.vitepress/config.mjs @@ -0,0 +1,46 @@ +import { defineConfig } from "vitepress"; + +export default defineConfig({ + title: "Query/Response", + description: "A VitePress Site", + lastUpdated: true, + themeConfig: { + nav: [{ text: "Home", link: "/" }], + sidebar: [ + { + text: "Guide", + items: [ + { + text: "What is Query/Response", + link: "/guide/what-is-query-response", + }, + { text: "Getting started", link: "/guide/getting-started" }, + { text: "Example revisited", link: "/guide/the-example-revisited"} + ], + }, + { + text: "Reference", + items: [ + { + text: "Specification", + link: "/reference/the-query-response-specification", + }, + { + text: "Maturity Model", + link: "/reference/query-response-maturity-model", + }, + ], + }, + ], + socialLinks: [ + { + icon: "github", + link: "https://github.com/olle/query-response-spring-amqp", + }, + ], + footer: { + message: "Published under the Apache-2.0 license", + copyright: `Copyright © 2019-${new Date().getFullYear()} Olle Törnström and all other contributors.`, + }, + }, +}); diff --git a/xdocs/attributes.js b/xdocs/attributes.js new file mode 100644 index 0000000..2d512f2 --- /dev/null +++ b/xdocs/attributes.js @@ -0,0 +1,25 @@ +// code-src: ../java/com/studiomediatech/queryresponse, +// test-src: ../../test/java/com/studiomediatech/queryresponse, +// examples-src: ../../../examples, + +// java-required-version: 11, +// java-name: Java, +// java-name-and-version: {java-name} {java-required-version}, +// java-name-and-latest-version: {java-name} {java-version}, + +export const springname = "Spring"; +export const springamqpname = "Spring AMQP"; +export const springbootversion = "2.x"; +export const springbootverifiedversion = "3.0.3"; +export const springbootname = "Spring Boot"; +export const springbootnameandversion = `${springbootname} ${springbootversion}`; +export const springbootnameandverifiedversion = `${springbootname} ${springbootverifiedversion}`; + +export const qrcurrentversion = "0.0.0-SNAPSHOT"; +export const qrpfx = "Query/Response"; +export const qrname = `${qrpfx} for ${springamqpname}`; +export const qruiname = `${qrpfx} Monitoring UI`; +export const qrnameandversion = `${qrname} ${qrcurrentversion}`; + +// qr-gh-link: https://github.com/olle/query-response-spring-amqp, +// qr-ui-link: https://github.com/olle/query-response-spring-amqraw/main/ui/query-response-ui.jar, diff --git a/xdocs/guide/getting-started.md b/xdocs/guide/getting-started.md new file mode 100644 index 0000000..38a9d58 --- /dev/null +++ b/xdocs/guide/getting-started.md @@ -0,0 +1,160 @@ + + +# Getting Started + +{{attr.qrname}} makes it really easy to extend {spring-boot-name} stand-alone, +production-grade applications, that are using {spring-amqp-name}. We have taken +a working pattern for building highly decoupled evolving service architectures, +and wrapped it in a developer friendly library. + +## System Requirements + +{qr-name-and-version} requires at least **{spring-boot-name-and-version}** and +**{java-name-and-version}**, and should work for later releases too. We are +building and running it successfully with **{java-name-and-latest-version}** and +the **{spring-boot-name-and-verified-version}** version. + +## Installation & Configuration + +:Maven: https://maven.apache.org +:Gradle: https://gradle.org +:Quickstart: https://github.com/olle/query-response-spring-amqp#quickstart + +It is distributed as a {Maven}[Maven] dependency, and is known to work well with +Maven 3.3+. Using the dependency with {Gradle}[Gradle] should work too. Please +see the {Quickstart}[Quickstart] information, available on the project +{qr-gh-link}[Github page], for information on how to get the Maven dependency. + +Enabling {qr-name} is done by loading the `QueryResponseConfiguration` +class. The most simple way to do this, is by annotating your {spring-boot-name} +application with the `@EnableQueryResponse` annotation. + +## [source,java] + +<<<@../../examples/myapp/src/main/java/app/MyApp.java{java} + +## include::{examples-src}/myapp/src/main/java/app/MyApp.java[tags=install] + +NOTE: This annotation will do nothing more but to import the +`QueryResponseConfiguration` class. + +That's it! There is no more infrastructure code, wiring or setup that needs to +be done. **It's just that easy.** + +### Connecting to an AMQP broker + +Before you can run your application you need to make sure there is an AMQP +broker available. By default {spring-amqp-name} tries to connect to a +https://www.rabbitmq.com[RabbitMQ], running locally on port `5672`. + +Start an and run RabbitMQ using `docker`: + +.... +$ docker run -p 5672:5672 -p 15672:15672 rabbitmq:3-management +.... + +NOTE: The `3-management` tag will enable the RabbitMQ Management UI. When the +broker is running, it can be accessed at http://localhost:15672 with +username and password `guest`. + +Now running your application, will enable {qr-name}, connect to the broker and +create all the resources necessary on the broker. + +.... +$ mvn spring-boot:run +.... + +Now is a good time to use the RabbitMQ Management UI, available at +http://localhost:15672, to inspect the exchange, queues and bindings created +by {qr-name} by default. + +## Queries + +Publishing **queries** is a way for your application to ask for information that +it may need in order to accomplish tasks. Queries express a _need_, and are not +addressed to any specific service or component. + +{qr-name} makes it really really easy, to create and publish a query using +the `QueryBuilder`. + +## [source,java] + +## include::{examples-src}/myapp/src/main/java/app/Queries.java[tags=query] + +<1> Initiates a query for the term `marco`, with any results being consumed as, +or _mapped_ to, the type `String.class`. Returned results are always +gathered in a collection. Either **none, one or many** elements may be +returned. + +<2> Queries require a timeout, here we set it to `1000L` milliseconds. This +means that this specific query will **always** block for 1 second. + +<3> The query may not receive any responses, so it _always_ needs to specify +how that case should be handled. Default here is an empty collection, of +the declared return type `String.class`. + +==== +Hopefully this shows, how concise and powerful the `QueryBuilder` is, dealing +with results mapping, fault tolerance and default values in just a couple of +lines of code. +==== + +If you run the application now, it will publish a **query** to the message +broker, which we can see in the logs. + +.... +$ mvn spring-boot:run +... +c.s.queryresponse.RabbitFacade : |<-- Published query: marco - (Body:'{}' MessageProperties [headers={x-qr-published=1589642002076}, replyTo=94f0fff4-c4f3-4491-831d-00809edb6f95, contentType=application/json, contentLength=2, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0]) +.... + +At the moment there are no responses to be consumed, so after blocking for 1 +second, nothing is printed `STDOUT`. + +## Responses + +Building services, medium, large or _micro_ (who cares), that publish +**responses** to queries is also really easy with {qr-name}, using the +`ResponseBuilder`. + +## [source,java] + +## include::{examples-src}/myapp/src/main/java/app/Responses.java[tags=response] + +<1> Initializes a response to queries for `marco`, providing the type-hint on +how to map entries in the response. Set to `String.class` here. + +<2> The response `withAll()` will publish all elements in one single response. + +<3> And finally this response is provided the elements `"polo", "yolo"` as the +actual data to publish. _The builder varags method, used here, is mostly +for trying out {qr-name}, or for static responses._ + +==== +Again, the builder makes it really easy to create a responding service, without +any special setup or complicated configurations. +==== + +Now if you run the application again, with the response component registered +before the query publisher, it will publish the response. + +.... +$ mvn spring-boot:run +... +c.s.queryresponse.RabbitFacade : |<-- Published query: marco - (Body:'{}' MessageProperties [headers={x-qr-published=1589642489894}, replyTo=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b, contentType=application/json, contentLength=2, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0]) +c.s.queryresponse.Response : |--> Consumed query: marco +c.s.queryresponse.RabbitFacade : |<-- Published response: c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b - (Body:'{"elements":["polo","yolo"]}' MessageProperties [headers={x-qr-published=1589642489941}, contentType=application/json, contentEncoding=UTF-8, contentLength=28, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0]) +c.s.queryresponse.Query : |--> Received response message: MessageProperties [headers={x-qr-published=1589642489941}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=NON_PERSISTENT, priority=0, redelivered=false, receivedExchange=, receivedRoutingKey=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b, deliveryTag=1, consumerTag=amq.ctag-Q_ghWp4TWU9EYhi_rqErcg, consumerQueue=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b] +marco? polo +marco? yolo +.... + +Now you can see a full roundtrip of the **query** being published and consumed, +and the **response** being published and also consumed. And the finished output +is "polo" and "yolo" printed on `STDOUT`. + +NOTE: We are using the `@Order` annotation in our example only to ensure that +responses are built and registered before queries, when they are built +in one and the same app. diff --git a/xdocs/guide/the-example-revisited.md b/xdocs/guide/the-example-revisited.md new file mode 100644 index 0000000..c0de7da --- /dev/null +++ b/xdocs/guide/the-example-revisited.md @@ -0,0 +1,150 @@ +--- +outline: deep +--- + +# The example revisited + +Let's examine one of the most powerful aspects of using the Query/Response +pattern. If we think back to our [initial example](./what-is-query-response.md) +we published a query for books in the sci-fi genre. + +``` +query: books.sci-fi +reply-to: library/books.sci-fi#42 +``` + +We also learned that responses may come from different sources, with different +payloads and we are responsible for dealing with validation and duplicates etc. + +The query in this example uses only some minimal semantics to express the +genre of books requested, the term `sci-fi`. This is part of a contract from +our domain, together with rules on how any result payload should be presented. +The list of strings within quotes are not by accident, it is also by design. + +The Query/Response pattern does not enforce any structural rules for query, +address or response syntax. This must come from designers and developers. _I +would suggest, using [Domain Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design) +to leverage the power of a ubiquitous language in the queries_. + +All this together puts us in a position to allow change and evolution in our +system. + +## A better library protocol + +We have agreed on supporting _stars_ for book ratings, and different teams +scramble to their stations to extend for the new feature. + +We saw earlier that data returned was formed as a list of quoted strings, and +the contract for parsing was: "first quoted string per line is book title". + +``` +body: + "Neuromancer" +``` + +That rule and the capability to extend it, made it possible to agree on a new +optional format: "trailing key-values are properties". For example: + +``` +body: + "Neuromancer" isbn:9780307969958 stars:4 +``` + +This is great. Let's get to work. + +## Top-3 books have stars + +``` +query: books.sci-fi +reply-to: library/books.sci-fi#77 +``` + +At a later time a new query for science fiction books is published. Now, we +still must not assume anything about the service or collaborator publishing +the query. It may be that we have a new service running in our system, not yet +live, or an updated version of the first one - we don't need to know. + +``` +response: library/books.sci-fi#77 +body: + "Neuromancer" stars:3 + "Snow Crash" stars:5 + "I, Robot" stars:4 +``` + +The first response looks great, it's using the new extended protocol and +provides star-ratings with the top-3 sci-fi book list. + +## One of each flavour + +Another response is consumed: + +``` +response: library/books.sci-fi#77 +body: + "I, Robot" + "The Gods Themselves" + "Pebble in the Sky" +``` + +Oh, ok seems that we've received a response with only Asimov books again, and +sadly no stars. Luckily the protocol rules allows us to still use the response +if we choose to. + +``` +response: library/books.sci-fi#77 +body: + "I, Robot" stars:2 + "The Gods Themselves" + "Pebble in the Sky" stars:5 +``` + +And what is this now. We've consumed yet another response and it appears to be +the Asimov list again, but this time with star-ratings, but only for a few +titles. + +This is quite normal and shows us a really important and valuable aspect of +the Query/Response pattern. If we would pull the curtain back a bit, it could +be reasonable to assume that the publisher of Asimov books now exists in 2 +distinct versions. One supports the new updated format, and has a couple of +star-ratings set. The other appears to be the _older_ version. + +We have effectively seen how response publishers can evolve, and even exist +side-by-side, if care is taken to design a suitable payload protocol. + +_The backward compatibility of the payload format is not at all required in the +Query/Response pattern. Implementations could use version tags or classifiers +to check for compatibility at the consumer side._ + +::: warning Important! +The key point here is, the consumer is still responsible for asserting the +usefulness and value of the response information. Parsing, validating or +checking for version compatibility is required. +::: + +## Out with the old + +Let's jump forward and say that at some later time, the query for sci-fi books +is published again. + +``` +query: books.sci-fi +reply-to: library/books.sci-fi#88 +``` + +And this time, the only consumed response with Asimov books is the following: + +``` +response: library/books.sci-fi#88 +body: + "I, Robot" stars:3 + "The Gods Themselves" stars:3 + "Pebble in the Sky" stars:5 +``` + +We can almost certainly conclude that the original version of the Asimov +book service has been shut down. + +Again we can see how the Query/Response pattern helps in coping with a natural +evolution of the system. Services can be added, removed or upgraded at any +time. diff --git a/xdocs/guide/what-is-query-response.md b/xdocs/guide/what-is-query-response.md new file mode 100644 index 0000000..d56f410 --- /dev/null +++ b/xdocs/guide/what-is-query-response.md @@ -0,0 +1,216 @@ +--- +outline: deep +--- + +# What is Query/Response? + +Query/Response is an asynchronous non-blocking messaging pattern for building +highly decoupled evolving service architectures. + +## A simple example + +Let's learn about the Query/Response pattern by walking through a small +fictional example (no pun intended). The technical context is _messaging_ and +hints at some type of broker-based setup - in theory though, any asynchronous +communication could be used. The examples are only pseudo-code and plain-text +data, to keep things simple. + +### Any good sci-fi books out there? + +Let's publish a query. + +``` +query: books.sci-fi +reply-to: library/books.sci-fi#42 +``` + +The structure above captures all the basic components that a query should +communicate. The term `books.sci-fi` expresses the published _need_, and we +can easily understand that it's a _request_ for science fiction books. + +_The dot-notation is not at all required, the query can use any syntax that +fits the platform or programming language._ + +The query has an address where responses should be sent back to: +`library/books.sci-fi#42`. This is really important, not only in order to +receive responses, but also to avoid coupling the sender to the query. We +don't need to state who's publishing the query. The `reply-to` is just an +address, a location or _mailbox_ that can be used for replies. + +The address is only for this particular query, and it is made to be unique. +In this example `library/books.sci-fi#42` describes a topic `library`, and +then the unique mailbox or queue for the query with a hash-code +`books.sci-fi#42`. + +### The current top-3 books + +``` +response: library/books.sci-fi#42 +body: + "Neuromancer" + "Snow Crash" + "I, Robot" +``` + +We're in luck. We got a response! The information above represents a response +to the query we published. It's sent to the address from the query, and carries +a body or payload of information which may be of interest to us. + +The response does not have to say who it's from. This allows us to think about +exchange of information, without the notion of: _"A sends a request to B, +which responds to A"_. We are making sure that the services are decoupled from +each other, by letting the response be an _optional_ message, sent to the +_address_ instead of a reply to the _sender_. More about this later. + +### The Asimov collection + +Since our query was published as a notification, we're not bound to a single +reply. We can keep on consuming any number of responses that are sent to the +address we published. + +``` +response: library/books.sci-fi#42 +body: + "I, Robot" + "The Gods Themselves" + "Pebble in the Sky" +``` + +In this response we received a list of book titles which all have the same +author. The previous was a list with popular books. This reply even has one +entry which was already in the first response we received. + +This is of course not a problem, and it shows us a couple of important things. +Responses may come from different sources and contexts. This means that the +consumer of a response will have to assert the value or _usefulness_ of the +received information, and decide how to handle it. + +::: warning Important +The structure of a response should of course conform to some common, agreed +upon, format or data-shape. More on this later. +::: + +Considering all this, we need to remember [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle). Information should be liberally handled (interpreted), but publishing should be +done more conservatively. As a consumer of responses we just can't have a +guarantee that the information received is valid, well formed or not malicious. +We have to consume, convert and validate with great care. The decoupling in +the Query/Response patter has a price, and this is one part of it. + +::: danger Consideration +But is a published REST-endpoint, for POST requests, that much better? I +would argue that we still have the same requirements. To be able to handle +requests liberally, we have to convert and validate, with great care. But +we are coupling the client and server to each other and, what is perhaps +even worse, we're actually allowing the client to control the writing of +information inside the server. We have at least surrendered to that model +of thinking. The POST is a write operation! +::: + +_To really think and reason about who's controlling the write operation, can +be a very powerful concept in my view. And arguably, the further away we +can push this authority from the actual, internal act of writing, the less +we need to think about the complexity of both collaborators at once. This is +of course the essence of messaging. We could still achieve this with the REST +endpoint, but I would say that it is a lot harder to avoid thinking about +the effect of the returned response from the POST request. Even if it is +empty. We are caught in a lock-step or imperative model._ + +### No book lovers out there? + +Let's rewind the scenario a bit. Let's say we've just published the query, +but no responses arrive. What should we do? + +This is not a flaw in the design, but a specific part of the Query/Response +pattern. It is always up to the consumer of responses (the one that sent +the query), to decide _how long_ it will continue to read, or wait for any to +arrive at all. The pattern does not force this or make any promises. + +There might be responses. There may be none, a single one or a huge amount. +This is by design, and it forces us to think about important questions, early +in development. Fallback values, proper defaults, circuit-breakers and how +to deal with a flood of responses. + +::: warning Important +The most commonly asked question, by developers new to the Query/Response +pattern, is: "But what if there are no responses, what do I show the user?". +Exactly! Plan for that. This is something that should be considered early +in design and development. There might very well be a response, eventually, +but how long do you let the user wait for a result? +::: + +### Reprise, surprise + +Back to our original scenario. We've received both the top-3, as well as +a collection of Asimov books. And we're still open for more responses to the +published address. + +``` +response: library/books.sci-fi#42 +body: + "Neuromancer" + "Snow Crash" + "I, Robot" +``` + +Hey, what's this! We now received the same response and body payload, as +before. This is still not a problem, and it's not a flaw in the pattern. It +is not possible to avoid multiple responses, even from the same publisher. As +a consumer, we have to be ready to handle it. There is nothing wrong with this +response at all. + +_The consumer must handle this, and can't keep the entries in a simple list. If +we did, it would contain several duplicate entries. It would be enough to use +a set instead, so any duplicate entries would only be kept once._ + +### So, what's in the library? + +Let's see what we have. + +``` +query: library.sci-fi +reply-to: bookshelf/library.sci-fi#1337 +``` + +A new query is published and we understand the `query` term to mean that +there's an _interest_ in knowing what books are in the library. A successful +scenario could arrive at the following response being consumed. + +``` +response: bookshelf/library.sci-fi#1337 +body: + "Neuromancer" + "Snow Crash" + "I, Robot" + "The Gods Themselves" + "Pebble in the Sky" +``` + +Just as expected. + +### Inversion of flow + +What we've seen in this example scenario is actually an inversion of what +could have been implemented as a tightly coupled, chained set of synchronous +service calls: + +> A user whishes to view a list of science fiction books through the +> `Bookshelf` service, which needs to call the `Library` for the list. The +> `Library` service aggregates all sci-fi books by calls to 2 configured +> services: `Top-3` and `Authors`. Only after both service calls return, can +> the `Library` respond to the `Bookshelf` and the user is presented with +> a list of sci-fi books. + +In this type of system, not only are the calls aggregated in the total time, +effectively forcing the user to wait until all calls return, but also to the +availability of each service. This accumulates at the point of the user, +making it highly probable that viewing the list of books will fail. + +_There are many ways to work towards better and more resilient solutions, also +in the synchronous solution. I'm not trying to say that it is the wrong +model. The point I'm trying to make, is the very different way of thinking +that the Query/Response pattern forces us into from the start. Availability, +fallbacks, resilience and strict timeouts are called out as key-concepts._ + +_I hope this illustrates what's possible using this approach and that I've +sparked at least som interest in the Query/Response pattern. Later I will +extend on some of the features and caveats._ diff --git a/xdocs/index.md b/xdocs/index.md new file mode 100644 index 0000000..99881af --- /dev/null +++ b/xdocs/index.md @@ -0,0 +1,68 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "Query/Response" + text: "For Spring® AMQP" + tagline: "A messaging pattern for building highly decoupled evolving service architectures." + + actions: + - theme: brand + text: What is Query/Response? + link: /guide/what-is-query-response + - theme: alt + text: Getting started + link: /guide/getting-started + - theme: alt + text: GitHub + link: https://github.com/olle/query-response-spring-amqp + +features: + - icon: + title: Resilient + details: Build safer and more resilient distributed services. + - icon: + title: Asynchronous + details: Get the benefits of an always async approach to data exchange. + - icon: + title: Decoupled + details: Ensure decoupling of components. Create scalable solutions. + - icon: + title: Evolving + details: Be better prepared for system evolution. +--- + +## Change the way you think and design, by using Query/Response for Spring AMQP. + +_Sometime around 2015 I came across a presentation with [Fred George](https://twitter.com/fgeorge52), +about the [Challenges in Implementing Microservices](https://youtu.be/yPf5MfOZPY0). +It's a great talk, with lots of good and relevant information. Experience comes +from learning through failures, and at this point in time I had just learned a +hard lesson about the problems with distributed services and blocking API calls. +I had seen how latencies would go up and availability go down the drain, as +calls from service A to B were actually depending on service B calling service +C, calling service D. It was a mess._ + +_In his talk George lands at the question "Synchronous or Asynchronous?" +and proceeds to describe, what he calls, the "Needs Pattern". Service A would, +instead of calling service B, publish a **query**, and service B would listen +for it and send back a **response**. After hearing this I began to think a lot +about the effects of moving to asynchronous communication between services. +There was clearly a lot more there than just decoupling. Something more +fundamental._ + +_The **Query/Response** pattern, that I arrived at, challenges developers to +really think hard about the responsibilities and autonomy of services. It +provides very few guarantees, which will force decisions around resilience +and availability at a much earlier stage in the design and development process. +It literally turns things around - an inversion of responsibility - which I +truly believe we can benefit from._ + +_Olle Törnström, 2019_ + diff --git a/xdocs/reference/query-response-maturity-model.md b/xdocs/reference/query-response-maturity-model.md new file mode 100644 index 0000000..9fa59b6 --- /dev/null +++ b/xdocs/reference/query-response-maturity-model.md @@ -0,0 +1,64 @@ +# Query/Response Maturity Model + +Just like with the [Richardson Maturity Model](https://martinfowler.com/articles/richardsonMaturityModel.html), +I've identified an evolution of maturity around the acceptance, use and +implementation of Query/Response. It describes the benefits, opportunities and +also complexities, pretty well. + +## Level 0 - Purgatory + +All communication and exchange is bound to fixed, configured, service end- +points. Synchronous blocking calls exchange information based on formats +declared in project Wiki-pages or Word-documents. Most solutions are stateless, +with I/O bound performance. Changes typically require system wide, +synchronized, upgrades. This lead to development dropping in velocity, as each +module or team will find it hard or impossible to act independently of each +other. + +## Level 1 + +Using the Query/Response pattern for the first time often leads to healthy +temporal decoupling pretty quick. But with a lot of code still written with +a synchronous model in mind, the data exchange tend to look a bit like _sync_. +Solutions move towards being stateful, but loosen their I/O-bound performance +characteristics. It's hard for developer to think about queries and responses +not coming from known components. Already at this level teams and modules gain +a lot in the capability to move independently. Releases and deployment is +practically not a tangle any more, although the view on evolutionary +data-structures or protocols for data, may lag behind and still be +Wiki/Document-based. + +## Level 2 + +At this level a deeper insight into the value of a proper data-structure or +protocol for payload, which can evolve as required, is often gained. With +this comes the extended benefit of seamless upgrades and service evolution. +Developers get to experience how responsibilities can move without breaking +or changing any integration code - response publishers can change owners and +location. + +## Level 3 + +More and more ideas around reactivity and flexibility begin to take form. +Events can immediately trigger queries which may enrich a local context based +on current needs. This moves the design and use beyond a system using sync, +and durable persistent state, to a more ephemeral and _living_ model. The data +structure of payloads tend to be less bound to strict _types_ and more +malleable _data shapes_. + +## Level 4 and beyond... + +Information exchange using the Query/Response pattern allows for almost +limitless evolution of services, or components, no longer bound to versions or +availability. The structure of any data is also very dynamic, information can +be partial, enriched, or come in different sets, from different publishers. No +schema is required at this level, but _data shapes_ are used, which can be +embraced by all collaborators in the architecture. Queries are sent and +responses consumed, sometimes within deliberate timeouts; take-until semantics. +This way modules can provide, and fulfill, explicit SLAs if required. + +::: info +This is of course not supposed to be taken too seriously, but the maturity +levels describes a journey, from a strict and stale model, into one where +evolution and change is quite normal. +::: diff --git a/xdocs/reference/the-query-response-specification.md b/xdocs/reference/the-query-response-specification.md new file mode 100644 index 0000000..c95e29a --- /dev/null +++ b/xdocs/reference/the-query-response-specification.md @@ -0,0 +1,167 @@ +# Query/Response Specification + +I'd like to describe the Query/Response pattern in a more formal but not +too strict way, since it's not in any way some type of _standard_ or +_protocol_. This is a pattern derived from the general idea of expressing a +_need_ or _demand_, as previously told. It is shaped here, into a specific +version, or flavour, in the **Query/Response pattern**. It simply contains +my recommendations and suggestions on rules or principles to follow. + +Please, take what you like, leave the rest, and extend as you seem fit. + +Use of the keywords: "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" are intended +to follow the definitions of [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). + +## Intent + +The Query/Response pattern aims to describe a model for information sharing +in a distributed system. It does so by using strong decoupling of system +actors and establishing asynchronous message-based high-level data exchange, +as the only means of communication. + +The following specifications tries to provide a set of rules and guides, +which can be used as an authoritative source for developer, implementing the +pattern. + +## Components and Collaborators + +| Name | Type | Description | +| ----------- | -------- | ----------- | +| `Query` | message | Very small, published notification. +| `Response` | message | Carries information as payload. +| `Address` | location | Reference to "a mailbox" +| `Publisher` | actor | Initiates _publish_ method calls. +| `Consumer` | actor | Accepts _consume_ method calls. + +### `Query` + +A notification that expresses a specific _need_ or _whish_, which can be +fulfilled by a response, published to a specified return address. The query +MUST state its _need_ or _whish_ in an interpretable way. It may use any +suitable syntax, semantics or language. Most commonly a simple string or term +is used, similar to a message subject, -name or an event _routing-key_. A +query MUST specify an address for responses, which SHOULD be _appropriate_ +for the stated query and, technically _available_, as the query is created. + +::: tip Recommendation +I very much recommend creating queries with expressions or terms from a +domain specific, or ubiquitous language. This allows for broader understanding +and involvement of stakeholders. Keeping queries human readable makes sense. +It's often desirable to use structured terms, with semantics, such as +filters or parameters. This is quite common and not at all bad. +::: + +### `Response` + +A notification, published, as a response to a query, optionally carrying an +information- or data-payload. A response MUST NOT be sent without an intent to +_answer_ a specific query (use event notifications for that). The response +MUST be sent to the address of the query it responds to, without manipulating +it. A response SHOULD carry an appropriate information- or data-payload, with +the intent to answer the query it responds to. Note that this is not a strict +requirement. Responses SHOULD be sent within an appropriate time frame of +seeing a query. + +::: tip +In most cases it's desirable to publish a response as quick as possible, +after consuming a query. +::: + +### `Address` + +Describes and designates an addressable _location_ with the capability to +receive and handle responses. Typically a messaging _mailbox_ or a queue. The +address MUST NOT describe a system actor or collaborator, but instead ensure +decoupling between a publisher and a consumer. + +::: tip +In messaging or broker based systems, the address is typically a routing key, +topic or a queue-name. +::: + +### `Publisher` + +An actor that initiates the publishing of a notification, either a query or +a response depending on its current role. The publisher MUST NOT be responsible +for the arrival of any published information. Publishers MUST NOT know any +consumers. + +::: info Note +The concrete _interpolated_ roles `Query-Publisher` and +`Response-Publisher`, does not have to be bound to a single or unique actor. +::: + +_It is open for the implementation of the Query/Response pattern to solve or +choose how it ensures delivery of messages, e.g. using a broker- or queue- +based messaging system or some other solution for asynchronous communication._ + +### `Consumer` + +An actor that willingly yields to the consumption of notifications, from some +external source, either a response or a query depending on its current role. +Consumers MUST NOT know any publishers. + +::: info Note +The concrete _interpolated_ roles `Query-Consumer` and +`Response-Consumer`, does not have to be bound to a single or unique actor. +::: + +## Methods and Actions + +_Nothing in the Query/Response pattern is synchronous, or based on the notion +of guaranteed delivery (or only-once semantics). The following structured +step-by-step description is only for documentation purposes, and does not, +in any way, define a sequence which can be relied upon._ + +### Prepare `Address` + +Before publishing a query, the query publisher SHOULD ensure that an +appropriate address, specified for the query, can be handled. + +::: info Note +Implementations are free to use a best-effort approach. It may be that the +only option is to use short-lived or temporary resources, which may or may +not fail to be allocated. Therefore there's no strict requirement to ensure +that the address can be handled. +::: + +### Publish `Query` + +The query publisher can, at any time, choose to publish a query. No ACK or +NACK will be provided and the query publisher MUST NOT assume that the query +has been consumed, or that a response will be returned at this time. The +publisher SHOULD consider the case where the query is lost, examine options +to detect and repair this, if possible; _timeouts, retries or fallbacks are +perhaps options to investigate_. + +### Consume `Query` + +A query consumer, that is willingly listening for queries, may at any time +receive, and choose to handle a query. Consuming queries is an unbound +operation. The consumer SHOULD handle queries with an intent to provide a +response, or ignore the query. A consumer MAY decide to publish none, one or +any number of responses to the query - it is optional. A consumer MAY at any +time choose to stop listening for queries. + +::: tip +Please note that the Query/Response pattern does not protect against +query consumers with harmful intent. Implementations should consider issues +like security, encryption and trust as extensions to it. +::: + +### Publish `Response` + +A response publisher MUST use the provided address of the query it responds to, +when publishing responses. No ACK or NACK will be provided and the publisher +MUST NOT assume that the response has been delivered, arrived properly or +consumed. + +### Consume `Response` + +A response consumer, listening for responses at a previously created address, +MAY at any time receive one or several responses - or not at all. Consuming +responses is an unbounded operation. Any received response MAY have a payload +or body of information. The consumer SHOULD assert and validate any +transferred information with great care. A consumer MAY at any time choose to +stop listening for responses. From 02cda8ad8de3c088472e7124c58e376f457dcab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Tue, 9 Jul 2024 20:23:22 +0200 Subject: [PATCH 05/12] Fixed up getting-started --- examples/myapp/src/main/java/app/MyApp.java | 4 +- examples/myapp/src/main/java/app/Queries.java | 4 +- .../myapp/src/main/java/app/Responses.java | 4 +- xdocs/attributes.js | 25 ---- xdocs/guide/getting-started.md | 116 +++++++++--------- 5 files changed, 63 insertions(+), 90 deletions(-) delete mode 100644 xdocs/attributes.js diff --git a/examples/myapp/src/main/java/app/MyApp.java b/examples/myapp/src/main/java/app/MyApp.java index 1975e14..0fd222a 100644 --- a/examples/myapp/src/main/java/app/MyApp.java +++ b/examples/myapp/src/main/java/app/MyApp.java @@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; -// tag::install[] +// #region install @SpringBootApplication @EnableQueryResponse public class MyApp { @@ -16,4 +16,4 @@ public static void main(String[] args) { SpringApplication.run(MyApp.class, args); } } -// end::install[] +// #endregion install diff --git a/examples/myapp/src/main/java/app/Queries.java b/examples/myapp/src/main/java/app/Queries.java index 97b04d3..3112fd4 100644 --- a/examples/myapp/src/main/java/app/Queries.java +++ b/examples/myapp/src/main/java/app/Queries.java @@ -15,7 +15,7 @@ import java.util.Collection; -// tag::query[] +// #region query @Component public class Queries { @@ -34,4 +34,4 @@ public void query() { polos.stream().map("marco? "::concat).forEach(System.out::println); } } -// end::query[] +// #endregion query diff --git a/examples/myapp/src/main/java/app/Responses.java b/examples/myapp/src/main/java/app/Responses.java index cf13915..3c79fcd 100644 --- a/examples/myapp/src/main/java/app/Responses.java +++ b/examples/myapp/src/main/java/app/Responses.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; -// tag::response[] +// #region response @Component public class Responses { @@ -29,4 +29,4 @@ public void response() { .from("polo", "yolo"); // <3> } } -// end::response[] +// #endregion response diff --git a/xdocs/attributes.js b/xdocs/attributes.js deleted file mode 100644 index 2d512f2..0000000 --- a/xdocs/attributes.js +++ /dev/null @@ -1,25 +0,0 @@ -// code-src: ../java/com/studiomediatech/queryresponse, -// test-src: ../../test/java/com/studiomediatech/queryresponse, -// examples-src: ../../../examples, - -// java-required-version: 11, -// java-name: Java, -// java-name-and-version: {java-name} {java-required-version}, -// java-name-and-latest-version: {java-name} {java-version}, - -export const springname = "Spring"; -export const springamqpname = "Spring AMQP"; -export const springbootversion = "2.x"; -export const springbootverifiedversion = "3.0.3"; -export const springbootname = "Spring Boot"; -export const springbootnameandversion = `${springbootname} ${springbootversion}`; -export const springbootnameandverifiedversion = `${springbootname} ${springbootverifiedversion}`; - -export const qrcurrentversion = "0.0.0-SNAPSHOT"; -export const qrpfx = "Query/Response"; -export const qrname = `${qrpfx} for ${springamqpname}`; -export const qruiname = `${qrpfx} Monitoring UI`; -export const qrnameandversion = `${qrname} ${qrcurrentversion}`; - -// qr-gh-link: https://github.com/olle/query-response-spring-amqp, -// qr-ui-link: https://github.com/olle/query-response-spring-amqraw/main/ui/query-response-ui.jar, diff --git a/xdocs/guide/getting-started.md b/xdocs/guide/getting-started.md index 38a9d58..b3a2543 100644 --- a/xdocs/guide/getting-started.md +++ b/xdocs/guide/getting-started.md @@ -1,41 +1,33 @@ # Getting Started -{{attr.qrname}} makes it really easy to extend {spring-boot-name} stand-alone, -production-grade applications, that are using {spring-amqp-name}. We have taken +{{BRAND}} makes it really easy to extend Spring Boot stand-alone, +production-grade applications, that are using Spring AMQP. We have taken a working pattern for building highly decoupled evolving service architectures, and wrapped it in a developer friendly library. ## System Requirements -{qr-name-and-version} requires at least **{spring-boot-name-and-version}** and -**{java-name-and-version}**, and should work for later releases too. We are -building and running it successfully with **{java-name-and-latest-version}** and -the **{spring-boot-name-and-verified-version}** version. +{{BRAND}} requires at least **Spring Boot 2.x** and **Java 11**, and should work +for later releases too. We are building and running it successfully with +**Java 11** and the **Spring Boot 3.0.3** version. ## Installation & Configuration -:Maven: https://maven.apache.org -:Gradle: https://gradle.org -:Quickstart: https://github.com/olle/query-response-spring-amqp#quickstart +It is distributed as a [Maven](https://maven.apache.org) dependency, and is +known to work well with Maven 3.3+. Using the dependency with [Gradle](https://gradle.org) +should work too. Please see the [Quickstart](https://github.com/olle/query-response-spring-amqp#quickstart) +information, available on the project [Github page](https://github.com/olle/query-response-spring-amqp), +for information on how to get the Maven dependency. -It is distributed as a {Maven}[Maven] dependency, and is known to work well with -Maven 3.3+. Using the dependency with {Gradle}[Gradle] should work too. Please -see the {Quickstart}[Quickstart] information, available on the project -{qr-gh-link}[Github page], for information on how to get the Maven dependency. - -Enabling {qr-name} is done by loading the `QueryResponseConfiguration` -class. The most simple way to do this, is by annotating your {spring-boot-name} +Enabling {{BRAND}} is done by loading the `QueryResponseConfiguration` +class. The most simple way to do this, is by annotating your Spring Boot application with the `@EnableQueryResponse` annotation. -## [source,java] - -<<<@../../examples/myapp/src/main/java/app/MyApp.java{java} - -## include::{examples-src}/myapp/src/main/java/app/MyApp.java[tags=install] +<<<@../../examples/myapp/src/main/java/app/MyApp.java#install{java} NOTE: This annotation will do nothing more but to import the `QueryResponseConfiguration` class. @@ -46,29 +38,29 @@ be done. **It's just that easy.** ### Connecting to an AMQP broker Before you can run your application you need to make sure there is an AMQP -broker available. By default {spring-amqp-name} tries to connect to a +broker available. By default {{BRAND}} tries to connect to a https://www.rabbitmq.com[RabbitMQ], running locally on port `5672`. Start an and run RabbitMQ using `docker`: -.... +```sh $ docker run -p 5672:5672 -p 15672:15672 rabbitmq:3-management -.... +``` NOTE: The `3-management` tag will enable the RabbitMQ Management UI. When the broker is running, it can be accessed at http://localhost:15672 with -username and password `guest`. +username and password `guest/guest`. -Now running your application, will enable {qr-name}, connect to the broker and +Now running your application, will enable {{BRAND}}, connect to the broker and create all the resources necessary on the broker. -.... +```sh $ mvn spring-boot:run -.... +``` Now is a good time to use the RabbitMQ Management UI, available at http://localhost:15672, to inspect the exchange, queues and bindings created -by {qr-name} by default. +by {{BRAND}} by default. ## Queries @@ -76,39 +68,47 @@ Publishing **queries** is a way for your application to ask for information that it may need in order to accomplish tasks. Queries express a _need_, and are not addressed to any specific service or component. -{qr-name} makes it really really easy, to create and publish a query using +{{BRAND}} makes it really really easy, to create and publish a query using the `QueryBuilder`. -## [source,java] +<<<@../../examples/myapp/src/main/java/app/Queries.java#query{java} -## include::{examples-src}/myapp/src/main/java/app/Queries.java[tags=query] +::: info Note +We are using the `@Order` annotation in our example only to ensure that +responses are built and registered before queries, when they are built +in one and the same app. +::: -<1> Initiates a query for the term `marco`, with any results being consumed as, +::: tip <1> +Initiates a query for the term `marco`, with any results being consumed as, or _mapped_ to, the type `String.class`. Returned results are always gathered in a collection. Either **none, one or many** elements may be returned. +::: -<2> Queries require a timeout, here we set it to `1000L` milliseconds. This +::: tip <2> +Queries require a timeout, here we set it to `1000L` milliseconds. This means that this specific query will **always** block for 1 second. +::: -<3> The query may not receive any responses, so it _always_ needs to specify +::: tip <3> +The query may not receive any responses, so it _always_ needs to specify how that case should be handled. Default here is an empty collection, of the declared return type `String.class`. +::: -==== -Hopefully this shows, how concise and powerful the `QueryBuilder` is, dealing +_Hopefully this shows, how concise and powerful the `QueryBuilder` is, dealing with results mapping, fault tolerance and default values in just a couple of -lines of code. -==== +lines of code._ If you run the application now, it will publish a **query** to the message broker, which we can see in the logs. -.... +```sh $ mvn spring-boot:run ... c.s.queryresponse.RabbitFacade : |<-- Published query: marco - (Body:'{}' MessageProperties [headers={x-qr-published=1589642002076}, replyTo=94f0fff4-c4f3-4491-831d-00809edb6f95, contentType=application/json, contentLength=2, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0]) -.... +``` At the moment there are no responses to be consumed, so after blocking for 1 second, nothing is printed `STDOUT`. @@ -116,31 +116,33 @@ second, nothing is printed `STDOUT`. ## Responses Building services, medium, large or _micro_ (who cares), that publish -**responses** to queries is also really easy with {qr-name}, using the +**responses** to queries is also really easy with {{BRAND}}, using the `ResponseBuilder`. -## [source,java] +<<<@../../examples/myapp/src/main/java/app/Responses.java#response{java} -## include::{examples-src}/myapp/src/main/java/app/Responses.java[tags=response] - -<1> Initializes a response to queries for `marco`, providing the type-hint on +::: tip <1> +Initializes a response to queries for `marco`, providing the type-hint on how to map entries in the response. Set to `String.class` here. +::: -<2> The response `withAll()` will publish all elements in one single response. +::: tip <2> +The response `withAll()` will publish all elements in one single response. +::: -<3> And finally this response is provided the elements `"polo", "yolo"` as the +::: tip <3> +And finally this response is provided the elements `"polo", "yolo"` as the actual data to publish. _The builder varags method, used here, is mostly for trying out {qr-name}, or for static responses._ +::: -==== -Again, the builder makes it really easy to create a responding service, without -any special setup or complicated configurations. -==== +_Again, the builder makes it really easy to create a responding service, without +any special setup or complicated configurations._ Now if you run the application again, with the response component registered before the query publisher, it will publish the response. -.... +```sh $ mvn spring-boot:run ... c.s.queryresponse.RabbitFacade : |<-- Published query: marco - (Body:'{}' MessageProperties [headers={x-qr-published=1589642489894}, replyTo=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b, contentType=application/json, contentLength=2, deliveryMode=NON_PERSISTENT, priority=0, deliveryTag=0]) @@ -149,12 +151,8 @@ c.s.queryresponse.RabbitFacade : |<-- Published response: c77a8a1d-c959-4f2a-bd5 c.s.queryresponse.Query : |--> Received response message: MessageProperties [headers={x-qr-published=1589642489941}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=NON_PERSISTENT, priority=0, redelivered=false, receivedExchange=, receivedRoutingKey=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b, deliveryTag=1, consumerTag=amq.ctag-Q_ghWp4TWU9EYhi_rqErcg, consumerQueue=c77a8a1d-c959-4f2a-bd51-85b7e6b5b69b] marco? polo marco? yolo -.... +``` Now you can see a full roundtrip of the **query** being published and consumed, and the **response** being published and also consumed. And the finished output is "polo" and "yolo" printed on `STDOUT`. - -NOTE: We are using the `@Order` annotation in our example only to ensure that -responses are built and registered before queries, when they are built -in one and the same app. From e3aa74399ffb8bec1d791488acdc7b9c161e7c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Tue, 9 Jul 2024 20:46:43 +0200 Subject: [PATCH 06/12] Add dev ref skeleton --- xdocs/.vitepress/config.mjs | 10 +- xdocs/reference/developers-reference.md | 323 ++++++++++++++++++ ...tion.md => the-query-response-protocol.md} | 2 +- 3 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 xdocs/reference/developers-reference.md rename xdocs/reference/{the-query-response-specification.md => the-query-response-protocol.md} (99%) diff --git a/xdocs/.vitepress/config.mjs b/xdocs/.vitepress/config.mjs index 406eebd..5126c07 100644 --- a/xdocs/.vitepress/config.mjs +++ b/xdocs/.vitepress/config.mjs @@ -15,15 +15,19 @@ export default defineConfig({ link: "/guide/what-is-query-response", }, { text: "Getting started", link: "/guide/getting-started" }, - { text: "Example revisited", link: "/guide/the-example-revisited"} + { text: "Example revisited", link: "/guide/the-example-revisited" }, ], }, { text: "Reference", items: [ { - text: "Specification", - link: "/reference/the-query-response-specification", + text: "Developers Reference", + link: "/reference/developers-reference", + }, + { + text: "Query/Response Protocol", + link: "/reference/the-query-response-protocol", }, { text: "Maturity Model", diff --git a/xdocs/reference/developers-reference.md b/xdocs/reference/developers-reference.md new file mode 100644 index 0000000..d58618c --- /dev/null +++ b/xdocs/reference/developers-reference.md @@ -0,0 +1,323 @@ +--- +outline: deep +--- + +# Developers Reference + +include::attributes.adoc[] + +## Overview + +The goal of {qr-name} is to provide developers with tools that are easy to use +and understand. We believe that a procedural and imperative style of writing +programs, or thinking about tasks in programs, is broadly understood and +valuable model. With {qr-name} we try to support this, rather than introducing +any new concepts for _streams_, _futures_ or _promises_. + +:DISTR: https://en.wikipedia.org/wiki/Distributed_computing + +With {qr-name} developers should feel enabled to write code in a _normal_ way. +However, it is our mission to raise awareness of things that are hard to +consider, when building {DISTR}[distributed systems]. The tools try to convey +these considerations, by making them transparent and part of the API. + +In the next couple of sections we'll look closer at the `QueryBuilder` and the +`ResponseBuilder` types. We'll discuss how to use them in detail, and try to +explain the concepts behind them, and the intention of their implementation. + +## `QueryBuilder` + +The `QueryBuilder` class is a central point of entry, and provides a fluent +builder-API, for publishing queries. It's provided as a bean, by enabling +{qr-name}, using the `@EnableQueryResponse` annotation. It may be injected +as a dependency to provide access from methods in any Spring component. + +We recommend injecting it via the component constructor, and keeping it as a +private field. The `findAuthors()` method below, shows how to access the +`queryBuilder` field in order to publish a query. + +[source,java] +---- +include::{examples-src}/querying/src/main/java/examples/Authors.java[tags=class] +---- + +In the example above, the published query is defined by the string **term** +`"authors"`. This is how the most basic contract of {qr-pfx} is defined. Any +string or text term may be published as a query. + +The second argument is the expected type of any received response elements. It +is not published with the query, but rather used to coerce or interpret any +received responses. This means that regardless of the payload of any response, +in this case {qr-name} will attempt to read the response elements as the +declared type `String.class`. + +Queries are built and published using the `queryFor(..)` _initial_ method. +Any following call to one of the _terminal_ methods `orEmpty()`, +`orDefaults(..)` and `orThrows(..)` will build and execute the query, and block +on the calling thread. + +Since the call above to `orEmpty()` blocks the thread, users have to specify +one or more query _conditionals_. In the example above, the call to +`waitingFor(..)` defines that the call will block for around 800 milliseconds. + +Constructing queries with the `QueryBuilder` revolves around creating a +composition of _initial_, _conditional_, maybe an optional _informal_ and +exactly one _terminal_ method call. In the table below is a short review of the +different builder methods and their types. + +[cols="1,1,3"] +.`QueryBuilder` fluid API method types +|=== +| Method | Type | Description + +| `queryFor(..)` | _initial_ | Creates a new builder for a query +| `waitingFor(..)` | _conditional_ | Specifies the waiting/blocking condition +| `takingAtMost(..)` | _conditional_ | Sets a limit condition, a maximum +| `takingAtLeast(..)` | _conditional_ | Sets a limit condition, a minimum +| `orEmpty()` | _terminal_ | Terminates with empty, after conditionals are evaluated +| `orDefaults(..)` | _terminal_ | Terminates with some defaults, after conditionals are evaluated +| `orThrow(..)` | _terminal_ | Terminates by throwing, after conditionals are evaluated +| `onError(..)` | _informal_ | Allows for explicit logging etc. +|=== + +Let's take a closer look at each of the builder method types. + +### _Initial_ methods + +At the moment there's only one _initial_ method and it's declared as: + +```java + public ChainingQueryBuilder queryFor(String term, Class type) +``` + +So we can query for any `String` **term** and given the expected mapped or +coerced **type** as a `Class`. The returned `ChainingQueryBuilder` +provides the capabilities of the fluid API. + +### _Conditional_ methods + +All _conditional_ properties can be composed together by the `QueryResponse` +builder API, to define whether a query is successful or not. If an executing +query is completed in a *successful* way, fulfilling the _conditionals_, it will +return and not consume any more responses. + +* `waitingFor(..)` - defines a timeout _conditional_. The built query will + evaluate as *successful* if _any_ responses were consumed after the + (approximate) given time limit has elapsed. There are a few different methods + declared, to specify the timeout: + +** `waitingFor(long millis)` +** `waitingFor(long amount, TemporalUnit timeUnit)` +** `waitingFor(Duration duration)` + +* `takingAtMost(int atMost)` - defines a limiting _conditional_ on the + aggregated number of received elements. The built query evaluates to + *successful*, and returns, when the given amount is reached. + +* `takingAtLeast(int atLeast)` - defines a minimum _conditional_ on the number + of received element. The built query evaluates to *successful*, only if at + least the given number of elements can be consumed. + +### _Terminal_ methods + +Only one _terminal_ method can be invoked on the builder, per query. It will +ensure that the query is built and executed. All _terminal_ methods are +declared to return `Collection` where the type parameter `` is given +in the _initial_ method `type` parameter. + +* `orEmpty()` - defines the query to return an empty `Collection` in case the + _conditionals_ do not evaluate to *successful*. + +* `orDefaults(..)` - defines the query to return with some provided _defaults_ + in case the _conditionals_ do not evaluate to *successful*. There are a couple + different methods declared for defaults: + +** `orDefaults(Collection defaults)` - set at _build-time_. + +** `orDefaults(Supplier> defaults)` - supplied at _run-time_. + +* `orThrow(..)` - defines the query to throw an exception in case the + _conditionals_ do not evaluate to *successful*. + +### _Informal_ methods + +Currently there's only one _informal_ builder method, allowing for extended +logging or information capture, in case the query fails or an exception is +thrown. + +```java + public ChainingQueryBuilder onError(Consumer handler) +``` + +TIP: Try to think more about how the `QueryBuilder` API covers the exceptional + query-cases, as part of the composition of _conditionals_. If clients try + to use _terminals_ that provide sensible defaults, it may not be necessary + to build other types of complex recovery or retries. + +### `QueryBuilder` examples + +Below are some examples of how the different `QueryBuilder` API methods can be +combined. + +Using `takingAtMost(..)`, combined with `waitingFor(..)`, system resources may +be preserved and the client can be protected from consuming too much data. + +```java + return queryBuilder.queryFor("authors", String.class) + .takingAtMost(10) + .waitingFor(800) + .orDefaults(Authors.defaults()); +``` + +It is possible to express constraints at the integration point, also when using +{qr-name}, throwing on an unfulfilled query, as an option to more lenient +handling with defaults. + +```java + return queryBuilder.queryFor("offers/rental", Offer.class) + .takingAtLeast(10) + .takingAtMost(20) + .waitingFor(2, ChronoUnit.SECONDS) + .orThrow(TooFewOffersConstraintException::new); +``` + +The _informal_ builder feature, allows for transparency into queries that may +have to be observed. + +```java + return queryBuilder.queryFor("offers/rental", NewOffer.class) + .takingAtLeast(3) + .waitingFor(400) + .onError(error -> LOG.error("Failure!", error)) + .orThrow(TooFewOffersConstraintException::new); +``` + +## `ResponseBuilder` + +Another entry-point into {qr-name} is the `ResponseBuilder`. It provides a +fluid builder-API that allows users to create responding services or components. + +It is also provided as a bean, when using the `@EnableQueryResponse` annotation +in a Spring application. It can easily be injected as a dependency to provide +access from methods in Spring components. + +The `respondWithAuthors()` method below, shows how the injected builder is used +to create a responding service. It is invoked by the Spring application context, +on the `ApplicationReadyEvent` event. + +[source,java] +---- +include::{examples-src}/responding/src/main/java/examples/OnlyThreeAuthors.java[tags=class] +---- + +In the example above the responding service is defined by calling the builder +method `respondTo(..)` with the query **term** parameter `"authors"`. It will +be bound to publish the given 3 authors as `String.class` entries, whenever it +consumes a query for the matching string **term** `"authors"`. + +This is the most basic premiss of {qr-pfx}, that any string or text term may be +interpreted as a query - it is however up to the response publisher to +determine what the query means. + +TIP: We've tried to provide information around the {qr-pfx} _protocol_ and + philosophy in the later chapter on <>. Go + there to find out more. + +The second parameter is the the type of each element, that will be published in +the response. It is given both as a type hint for the compiler, as well as a +parameter to the data mapper. Here it's trivial, the three authors are given as +`String.class` entries. + +NOTE: The data mapper mentioned above, is in fact the + `com.fasterxml.jackson.databind.ObjectMapper` and {qr-name} currently uses + JSON as the transport format. This means that type hints, JSON mapping + configuration annotations or custom mappings will apply. However as data + mapping on the consumer side is done by coercion, the published format + must conform to some agreed upon standard, shape or protocol. + +Response publishers are built using the `respondTo(..)` _initial_ method. Any +following call to one of the _terminal_ methods `from(..)` or `suppliedBy(..)` +will create and register it, as its own consumer in another thread. The +builder call returns immediately. + +The `ResponseBuilder` comes with some methods to allow for _partitioning_ or +_batching_, which can be used to control the transfer of data to some degree. + +The table below shows a summary of the builder methods and types. + +[cols="1,1,3"] +.`ResponseBuilder` fluid API method types +|=== +| Method | Type | Description + +| `respondTo(..)` | _initial_ | Creates a new builder for a query +| `withAll()` | _batching_ | Specifies NO batches +| `withBatchesOf(..)` | _batching_ | Sets the batch size of responses +| `from(..)` | _terminal_ | Terminates with some given response data +| `suppliedBy(..)` | _terminal_ | Terminates with some supplied response data +|=== + +Let's take a closer look at each of the builder method types. + +### _Initial_ methods + +At the moment there's only one _initial_ method for building responses. It is +declared as: + +```java + public ChainingResponseBuilder respondTo(String term, Class type) +``` + +So we can create a response for any `String` **term** and declare that we intend +to publish elements of some **type** given as a `Class`. The returned +`ChainingResponseBuilder` provides the capabilities of the fluid API. + +### _Batching_ methods + +Control over how response elements are published can be made by using the +_batching_ methods that the builder provides. + +* `withAll()` - defines that **no** batching should be used, and will publish + all given elements, or try to drain a supplied `Iterator` all at once. + +* `withBatchesOf(int size)` - defines a batch size, which the response publisher + will use, to create a series of response messages, with up-to the given `size` + of elements. + +### _Terminal_ methods + +Only one _terminal_ method can be called on the builder, per response. It will +ensure that a responder is created and added as a query-consumer, a subscriber +to the query **term** as a topic. It is not attached to the calling thread, so +the builder call always returns after the _terminal_ call. + +* `from(..)` - declares the source for the provided response data elements. It + is declared in a few different ways, for alternative use: + +** `from(T... elements)` - vararg elements +** `from(Collection elements)` - provided collection at _build-time_ +** `from(Supplier> elements)` - supplied iterator at _build-time_ + +* `suppliedBy(Supplier> elements)` - declares that response data + is supplied at _run-time_. + +### `ResponseBuilder` examples + +Batch responses provide developers with more options to tune and throttle a +system using {qr-pfx} across many services. It may tune and change the profile +of resource use, in a network. + +```java + responseBuilder.respondTo("offers/monday", Offer.class) + .withBatchesOf(20) + .from(offers.findAllOffersByDayOfWeek(Calendar.MONDAY)); +``` + +Dynamic responses are easy to build, with an API that suits modern Java, using +lazy calls to suppliers of data. + +```java + responseBuilder.respondTo("users/current", Token.class) + .withBatchesOf(128) + .suppliedBy(userTokenService::findAllCurrentUserTokens); +``` diff --git a/xdocs/reference/the-query-response-specification.md b/xdocs/reference/the-query-response-protocol.md similarity index 99% rename from xdocs/reference/the-query-response-specification.md rename to xdocs/reference/the-query-response-protocol.md index c95e29a..13ce981 100644 --- a/xdocs/reference/the-query-response-specification.md +++ b/xdocs/reference/the-query-response-protocol.md @@ -1,4 +1,4 @@ -# Query/Response Specification +# Query/Response Protocol I'd like to describe the Query/Response pattern in a more formal but not too strict way, since it's not in any way some type of _standard_ or From 4d7b2ee7d97424678995d403d9c0f3bcf0878840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Tue, 9 Jul 2024 23:22:50 +0200 Subject: [PATCH 07/12] Add search --- xdocs/.vitepress/config.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xdocs/.vitepress/config.mjs b/xdocs/.vitepress/config.mjs index 5126c07..53cceb3 100644 --- a/xdocs/.vitepress/config.mjs +++ b/xdocs/.vitepress/config.mjs @@ -5,6 +5,9 @@ export default defineConfig({ description: "A VitePress Site", lastUpdated: true, themeConfig: { + search: { + provider: "local", + }, nav: [{ text: "Home", link: "/" }], sidebar: [ { From 8bd4be7afcd31a0240df10cc2ae415cb3c9c7402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Tue, 9 Jul 2024 23:23:06 +0200 Subject: [PATCH 08/12] Add custom theme colors --- ui-frontend/src/style.css | 76 +------------------------------ ui-frontend/src/vars.css | 75 ++++++++++++++++++++++++++++++ xdocs/.vitepress/theme/custom.css | 7 +++ xdocs/.vitepress/theme/index.js | 5 ++ xdocs/index.md | 7 ++- 5 files changed, 94 insertions(+), 76 deletions(-) create mode 100644 ui-frontend/src/vars.css create mode 100644 xdocs/.vitepress/theme/custom.css create mode 100644 xdocs/.vitepress/theme/index.js diff --git a/ui-frontend/src/style.css b/ui-frontend/src/style.css index 2a62b51..d943a9b 100644 --- a/ui-frontend/src/style.css +++ b/ui-frontend/src/style.css @@ -12,81 +12,7 @@ html { /* THEME -------------------------------------------------------------------- */ -:root { - --color-black: rgba(44, 62, 80, 0.9); - - --color-fuchsia-100: hsl(344, 97%, 95%); - --color-fuchsia-200: hsl(344, 97%, 90%); - --color-fuchsia-300: hsl(344, 97%, 85%); - --color-fuchsia-400: hsl(344, 97%, 75%); - --color-fuchsia-500: hsl(344, 97%, 64%); - --color-fuchsia-600: hsl(344, 97%, 54%); - --color-fuchsia-700: hsl(344, 97%, 44%); - --color-fuchsia-800: hsl(344, 97%, 34%); - --color-fuchsia-900: hsl(344, 97%, 20%); - - --color-petrol-100: hsl(157, 30%, 95%); - --color-petrol-200: hsl(157, 30%, 90%); - --color-petrol-300: hsl(157, 30%, 80%); - --color-petrol-400: hsl(157, 30%, 70%); - --color-petrol-500: hsl(157, 30%, 63%); - --color-petrol-600: hsl(157, 30%, 53%); - --color-petrol-700: hsl(157, 30%, 40%); - --color-petrol-800: hsl(157, 30%, 30%); - --color-petrol-900: hsl(157, 30%, 20%); - - --color-blue-100: hsla(209, 58%, 95%); - --color-blue-200: hsla(209, 58%, 80%); - --color-blue-300: hsla(209, 58%, 70%); - --color-blue-400: hsla(209, 58%, 60%); - --color-blue-500: hsla(209, 58%, 50%); - --color-blue-600: hsla(209, 58%, 46%); - --color-blue-700: hsla(209, 58%, 36%); - --color-blue-800: hsla(209, 58%, 30%); - --color-blue-900: hsl(209, 59%, 20%); - - --color-amber: hsl(37, 100%, 55%); - - --bg: #f3f2eb; - --bg-hover: rgba(123, 154, 255, 0.07); - --bg-even: hsla(226, 10%, 50%, 0.07); - --fg: #111; - --fg-light: rgba(0, 0, 0, 0.4); - --link: rgba(50, 121, 187, 0.925); - --link-high-contrast: rgba(50, 121, 187, 1); - --panel: #fff; - --border: 1px solid rgba(182, 182, 182, 0.424); - --border-radius: 5px; - --action: var(--link-high-contrast); - --success: var(--color-petrol-600); - --warning: hsl(37, 100%, 55%); - --error: var(--color-fuchsia-500); - --bold: 600; - --thin: 400; - --bold-letter-spacing: 0.1rem; -} - -[data-theme="dark"] { - --bg: hsl(330, 10%, 23%); - --bg-hover: rgba(212, 222, 255, 0.07); - --bg-even: hsla(225, 10%, 62%, 0.164); - --fg: #fff; - --fg-light: rgba(255, 255, 250, 0.5); - --link: rgba(50, 121, 187, 0.925); - --link-high-contrast: rgb(104, 176, 243); - --panel: hsl(30, 1%, 38%); - --border: 1px solid rgba(0, 0, 0, 0.3); - --success: var(--color-petrol-400); - --warning: #ffba4c; - --error: var(--color-fuchsia-400); - --bold: 500; - --thin: 300; - --bold-letter-spacing: 0.127rem; -} - -[data-theme="dark"] .icon-tabler { - stroke-width: 1.6; -} +@import url("vars.css"); /* Main style --------------------------------------------------------------- */ diff --git a/ui-frontend/src/vars.css b/ui-frontend/src/vars.css new file mode 100644 index 0000000..678f504 --- /dev/null +++ b/ui-frontend/src/vars.css @@ -0,0 +1,75 @@ +:root { + --color-black: rgba(44, 62, 80, 0.9); + + --color-fuchsia-100: hsl(344, 97%, 95%); + --color-fuchsia-200: hsl(344, 97%, 90%); + --color-fuchsia-300: hsl(344, 97%, 85%); + --color-fuchsia-400: hsl(344, 97%, 75%); + --color-fuchsia-500: hsl(344, 97%, 64%); + --color-fuchsia-600: hsl(344, 97%, 54%); + --color-fuchsia-700: hsl(344, 97%, 44%); + --color-fuchsia-800: hsl(344, 97%, 34%); + --color-fuchsia-900: hsl(344, 97%, 20%); + + --color-petrol-100: hsl(157, 30%, 95%); + --color-petrol-200: hsl(157, 30%, 90%); + --color-petrol-300: hsl(157, 30%, 80%); + --color-petrol-400: hsl(157, 30%, 70%); + --color-petrol-500: hsl(157, 30%, 63%); + --color-petrol-600: hsl(157, 30%, 53%); + --color-petrol-700: hsl(157, 30%, 40%); + --color-petrol-800: hsl(157, 30%, 30%); + --color-petrol-900: hsl(157, 30%, 20%); + + --color-blue-100: hsla(209, 58%, 95%); + --color-blue-200: hsla(209, 58%, 80%); + --color-blue-300: hsla(209, 58%, 70%); + --color-blue-400: hsla(209, 58%, 60%); + --color-blue-500: hsla(209, 58%, 50%); + --color-blue-600: hsla(209, 58%, 46%); + --color-blue-700: hsla(209, 58%, 36%); + --color-blue-800: hsla(209, 58%, 30%); + --color-blue-900: hsl(209, 59%, 20%); + + --color-amber: hsl(37, 100%, 55%); + + --bg: #f3f2eb; + --bg-hover: rgba(123, 154, 255, 0.07); + --bg-even: hsla(226, 10%, 50%, 0.07); + --fg: #111; + --fg-light: rgba(0, 0, 0, 0.4); + --link: rgba(50, 121, 187, 0.925); + --link-high-contrast: rgba(50, 121, 187, 1); + --panel: #fff; + --border: 1px solid rgba(182, 182, 182, 0.424); + --border-radius: 5px; + --action: var(--link-high-contrast); + --success: var(--color-petrol-600); + --warning: hsl(37, 100%, 55%); + --error: var(--color-fuchsia-500); + --bold: 600; + --thin: 400; + --bold-letter-spacing: 0.1rem; +} + +[data-theme="dark"] { + --bg: hsl(330, 10%, 23%); + --bg-hover: rgba(212, 222, 255, 0.07); + --bg-even: hsla(225, 10%, 62%, 0.164); + --fg: #fff; + --fg-light: rgba(255, 255, 250, 0.5); + --link: rgba(50, 121, 187, 0.925); + --link-high-contrast: rgb(104, 176, 243); + --panel: hsl(30, 1%, 38%); + --border: 1px solid rgba(0, 0, 0, 0.3); + --success: var(--color-petrol-400); + --warning: #ffba4c; + --error: var(--color-fuchsia-400); + --bold: 500; + --thin: 300; + --bold-letter-spacing: 0.127rem; +} + +[data-theme="dark"] .icon-tabler { + stroke-width: 1.6; +} diff --git a/xdocs/.vitepress/theme/custom.css b/xdocs/.vitepress/theme/custom.css new file mode 100644 index 0000000..1b8e53a --- /dev/null +++ b/xdocs/.vitepress/theme/custom.css @@ -0,0 +1,7 @@ +:root { + font-size: 130%; + + --vp-c-brand-1: var(--color-petrol-800); + --vp-c-brand-2: var(--color-petrol-700); + --vp-c-brand-3: var(--color-petrol-600); +} \ No newline at end of file diff --git a/xdocs/.vitepress/theme/index.js b/xdocs/.vitepress/theme/index.js new file mode 100644 index 0000000..f0ac6e5 --- /dev/null +++ b/xdocs/.vitepress/theme/index.js @@ -0,0 +1,5 @@ +import DefaultTheme from "vitepress/theme"; +import "../../../ui-frontend/src/vars.css"; +import "./custom.css"; + +export default DefaultTheme; diff --git a/xdocs/index.md b/xdocs/index.md index 99881af..dbb23b2 100644 --- a/xdocs/index.md +++ b/xdocs/index.md @@ -33,6 +33,10 @@ features: details: Be better prepared for system evolution. --- + + ## Change the way you think and design, by using Query/Response for Spring AMQP. _Sometime around 2015 I came across a presentation with [Fred George](https://twitter.com/fgeorge52), @@ -60,9 +64,10 @@ It literally turns things around - an inversion of responsibility - which I truly believe we can benefit from._ _Olle Törnström, 2019_ + From 49b34065434ad5629b98fffa6f6fcc55507ea2bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Wed, 10 Jul 2024 06:57:21 +0200 Subject: [PATCH 09/12] Extend on dev reference --- .../src/main/java/examples/Authors.java | 4 +- .../main/java/examples/OnlyThreeAuthors.java | 4 +- xdocs/attributes.js | 1 + xdocs/guide/getting-started.md | 18 +- xdocs/reference/developers-reference.md | 206 +++++++++--------- 5 files changed, 120 insertions(+), 113 deletions(-) create mode 100644 xdocs/attributes.js diff --git a/examples/querying/src/main/java/examples/Authors.java b/examples/querying/src/main/java/examples/Authors.java index b250d58..254c157 100644 --- a/examples/querying/src/main/java/examples/Authors.java +++ b/examples/querying/src/main/java/examples/Authors.java @@ -7,7 +7,7 @@ import java.util.Collection; -//tag::class[] +// #region class @Component public class Authors { @@ -23,4 +23,4 @@ public Collection findAuthors() { .orEmpty(); } } -//end::class[] +// #endregion class diff --git a/examples/responding/src/main/java/examples/OnlyThreeAuthors.java b/examples/responding/src/main/java/examples/OnlyThreeAuthors.java index 0001f26..9c5bac6 100644 --- a/examples/responding/src/main/java/examples/OnlyThreeAuthors.java +++ b/examples/responding/src/main/java/examples/OnlyThreeAuthors.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Component; -//tag::class[] +// #region class @Component public class OnlyThreeAuthors { @@ -26,4 +26,4 @@ public void respondWithAuthors() { .from("Tolkien", "Lewis", "Rowling"); } } -//end::class[] +// #endregion class diff --git a/xdocs/attributes.js b/xdocs/attributes.js new file mode 100644 index 0000000..36514e4 --- /dev/null +++ b/xdocs/attributes.js @@ -0,0 +1 @@ +export const BRAND = "Query/Response for Spring® AMQP"; diff --git a/xdocs/guide/getting-started.md b/xdocs/guide/getting-started.md index b3a2543..403c007 100644 --- a/xdocs/guide/getting-started.md +++ b/xdocs/guide/getting-started.md @@ -1,17 +1,17 @@ # Getting Started -{{BRAND}} makes it really easy to extend Spring Boot stand-alone, +{{attr.BRAND}} makes it really easy to extend Spring Boot stand-alone, production-grade applications, that are using Spring AMQP. We have taken a working pattern for building highly decoupled evolving service architectures, and wrapped it in a developer friendly library. ## System Requirements -{{BRAND}} requires at least **Spring Boot 2.x** and **Java 11**, and should work +{{attr.BRAND}} requires at least **Spring Boot 2.x** and **Java 11**, and should work for later releases too. We are building and running it successfully with **Java 11** and the **Spring Boot 3.0.3** version. @@ -23,7 +23,7 @@ should work too. Please see the [Quickstart](https://github.com/olle/query-respo information, available on the project [Github page](https://github.com/olle/query-response-spring-amqp), for information on how to get the Maven dependency. -Enabling {{BRAND}} is done by loading the `QueryResponseConfiguration` +Enabling {{attr.BRAND}} is done by loading the `QueryResponseConfiguration` class. The most simple way to do this, is by annotating your Spring Boot application with the `@EnableQueryResponse` annotation. @@ -38,7 +38,7 @@ be done. **It's just that easy.** ### Connecting to an AMQP broker Before you can run your application you need to make sure there is an AMQP -broker available. By default {{BRAND}} tries to connect to a +broker available. By default {{attr.BRAND}} tries to connect to a https://www.rabbitmq.com[RabbitMQ], running locally on port `5672`. Start an and run RabbitMQ using `docker`: @@ -51,7 +51,7 @@ NOTE: The `3-management` tag will enable the RabbitMQ Management UI. When the broker is running, it can be accessed at http://localhost:15672 with username and password `guest/guest`. -Now running your application, will enable {{BRAND}}, connect to the broker and +Now running your application, will enable {{attr.BRAND}}, connect to the broker and create all the resources necessary on the broker. ```sh @@ -60,7 +60,7 @@ $ mvn spring-boot:run Now is a good time to use the RabbitMQ Management UI, available at http://localhost:15672, to inspect the exchange, queues and bindings created -by {{BRAND}} by default. +by {{attr.BRAND}} by default. ## Queries @@ -68,7 +68,7 @@ Publishing **queries** is a way for your application to ask for information that it may need in order to accomplish tasks. Queries express a _need_, and are not addressed to any specific service or component. -{{BRAND}} makes it really really easy, to create and publish a query using +{{attr.BRAND}} makes it really really easy, to create and publish a query using the `QueryBuilder`. <<<@../../examples/myapp/src/main/java/app/Queries.java#query{java} @@ -116,7 +116,7 @@ second, nothing is printed `STDOUT`. ## Responses Building services, medium, large or _micro_ (who cares), that publish -**responses** to queries is also really easy with {{BRAND}}, using the +**responses** to queries is also really easy with {{attr.BRAND}}, using the `ResponseBuilder`. <<<@../../examples/myapp/src/main/java/app/Responses.java#response{java} diff --git a/xdocs/reference/developers-reference.md b/xdocs/reference/developers-reference.md index d58618c..74e6f57 100644 --- a/xdocs/reference/developers-reference.md +++ b/xdocs/reference/developers-reference.md @@ -2,24 +2,25 @@ outline: deep --- -# Developers Reference + -include::attributes.adoc[] +# Developers Reference ## Overview -The goal of {qr-name} is to provide developers with tools that are easy to use +The goal of {{attr.BRAND}} is to provide developers with tools that are easy to use and understand. We believe that a procedural and imperative style of writing -programs, or thinking about tasks in programs, is broadly understood and -valuable model. With {qr-name} we try to support this, rather than introducing +programs, or thinking about tasks in programs, is broadly understood and a +very valuable model. With {{attr.BRAND}} we try to support this, rather than introducing any new concepts for _streams_, _futures_ or _promises_. -:DISTR: https://en.wikipedia.org/wiki/Distributed_computing - -With {qr-name} developers should feel enabled to write code in a _normal_ way. +With {{attr.BRAND}} developers should feel enabled to write code in a _normal_ way. However, it is our mission to raise awareness of things that are hard to -consider, when building {DISTR}[distributed systems]. The tools try to convey -these considerations, by making them transparent and part of the API. +consider, when building [distributed systems](https://en.wikipedia.org/wiki/Distributed_computing). +The tools try to convey these considerations, by making them transparent and +part of the API. In the next couple of sections we'll look closer at the `QueryBuilder` and the `ResponseBuilder` types. We'll discuss how to use them in detail, and try to @@ -29,26 +30,23 @@ explain the concepts behind them, and the intention of their implementation. The `QueryBuilder` class is a central point of entry, and provides a fluent builder-API, for publishing queries. It's provided as a bean, by enabling -{qr-name}, using the `@EnableQueryResponse` annotation. It may be injected +{{attr.BRAND}}, using the `@EnableQueryResponse` annotation. It may be injected as a dependency to provide access from methods in any Spring component. We recommend injecting it via the component constructor, and keeping it as a private field. The `findAuthors()` method below, shows how to access the `queryBuilder` field in order to publish a query. -[source,java] ----- -include::{examples-src}/querying/src/main/java/examples/Authors.java[tags=class] ----- +<<<@../../examples/querying/src/main/java/examples/Authors.java#class{java} -In the example above, the published query is defined by the string **term** -`"authors"`. This is how the most basic contract of {qr-pfx} is defined. Any -string or text term may be published as a query. +In the example above, the published query is defined by the string **term** +`"authors"`. This is how the most basic contract of Query/Response is defined. +Any string or text term may be published as a query. The second argument is the expected type of any received response elements. It is not published with the query, but rather used to coerce or interpret any received responses. This means that regardless of the payload of any response, -in this case {qr-name} will attempt to read the response elements as the +in this case {{attr.BRAND}} will attempt to read the response elements as the declared type `String.class`. Queries are built and published using the `queryFor(..)` _initial_ method. @@ -61,24 +59,23 @@ one or more query _conditionals_. In the example above, the call to `waitingFor(..)` defines that the call will block for around 800 milliseconds. Constructing queries with the `QueryBuilder` revolves around creating a -composition of _initial_, _conditional_, maybe an optional _informal_ and +composition of _initial_, _conditional_, an optional _informal_ and exactly one _terminal_ method call. In the table below is a short review of the different builder methods and their types. -[cols="1,1,3"] -.`QueryBuilder` fluid API method types -|=== -| Method | Type | Description - -| `queryFor(..)` | _initial_ | Creates a new builder for a query -| `waitingFor(..)` | _conditional_ | Specifies the waiting/blocking condition -| `takingAtMost(..)` | _conditional_ | Sets a limit condition, a maximum -| `takingAtLeast(..)` | _conditional_ | Sets a limit condition, a minimum -| `orEmpty()` | _terminal_ | Terminates with empty, after conditionals are evaluated -| `orDefaults(..)` | _terminal_ | Terminates with some defaults, after conditionals are evaluated -| `orThrow(..)` | _terminal_ | Terminates by throwing, after conditionals are evaluated -| `onError(..)` | _informal_ | Allows for explicit logging etc. -|=== + +### `QueryBuilder` fluid API method types + +| Method | Type | Description | +| ------------------- | ------------- | --------------------------------------------------------------- | +| `queryFor(..)` | _initial_ | Creates a new builder for a query | +| `waitingFor(..)` | _conditional_ | Specifies the waiting/blocking condition | +| `takingAtMost(..)` | _conditional_ | Sets a limit condition, a maximum | +| `takingAtLeast(..)` | _conditional_ | Sets a limit condition, a minimum | +| `orEmpty()` | _terminal_ | Terminates with empty, after conditionals are evaluated | +| `orDefaults(..)` | _terminal_ | Terminates with some defaults, after conditionals are evaluated | +| `orThrow(..)` | _terminal_ | Terminates by throwing, after conditionals are evaluated | +| `onError(..)` | _informal_ | Allows for explicit logging etc. | Let's take a closer look at each of the builder method types. @@ -87,7 +84,7 @@ Let's take a closer look at each of the builder method types. At the moment there's only one _initial_ method and it's declared as: ```java - public ChainingQueryBuilder queryFor(String term, Class type) +public ChainingQueryBuilder queryFor(String term, Class type) ``` So we can query for any `String` **term** and given the expected mapped or @@ -98,46 +95,52 @@ provides the capabilities of the fluid API. All _conditional_ properties can be composed together by the `QueryResponse` builder API, to define whether a query is successful or not. If an executing -query is completed in a *successful* way, fulfilling the _conditionals_, it will +query is completed in a _successful_ way, fulfilling the _conditionals_, it will return and not consume any more responses. -* `waitingFor(..)` - defines a timeout _conditional_. The built query will - evaluate as *successful* if _any_ responses were consumed after the +- `waitingFor(..)` - defines a timeout _conditional_. The built query will + evaluate as _successful_ if _any_ responses were consumed after the (approximate) given time limit has elapsed. There are a few different methods declared, to specify the timeout: -** `waitingFor(long millis)` -** `waitingFor(long amount, TemporalUnit timeUnit)` -** `waitingFor(Duration duration)` + - `waitingFor(long millis)` + - `waitingFor(long amount, TemporalUnit timeUnit)` + - `waitingFor(Duration duration)` -* `takingAtMost(int atMost)` - defines a limiting _conditional_ on the +- `takingAtMost(int atMost)` - defines a limiting _conditional_ on the aggregated number of received elements. The built query evaluates to - *successful*, and returns, when the given amount is reached. + _successful_, and returns, when the given amount is reached. -* `takingAtLeast(int atLeast)` - defines a minimum _conditional_ on the number - of received element. The built query evaluates to *successful*, only if at +- `takingAtLeast(int atLeast)` - defines a minimum _conditional_ on the number + of received element. The built query evaluates to _successful_, only if at least the given number of elements can be consumed. ### _Terminal_ methods -Only one _terminal_ method can be invoked on the builder, per query. It will -ensure that the query is built and executed. All _terminal_ methods are +Only one _terminal_ method can be invoked on the builder, per query. It +will ensure that the query is built and executed. All _terminal_ methods are declared to return `Collection` where the type parameter `` is given in the _initial_ method `type` parameter. -* `orEmpty()` - defines the query to return an empty `Collection` in case the - _conditionals_ do not evaluate to *successful*. +- `orEmpty()` - defines the query to return an empty `Collection` in case the + _conditionals_ do not evaluate to _successful_. -* `orDefaults(..)` - defines the query to return with some provided _defaults_ - in case the _conditionals_ do not evaluate to *successful*. There are a couple +- `orDefaults(..)` - defines the query to return with some provided _defaults_ + in case the _conditionals_ do not evaluate to _successful_. There are a couple different methods declared for defaults: -** `orDefaults(Collection defaults)` - set at _build-time_. + - `orDefaults(Collection defaults)` - set at _call-time_. + - `orDefaults(Supplier> defaults)` - supplied lazily at _run-time_. -** `orDefaults(Supplier> defaults)` - supplied at _run-time_. - -* `orThrow(..)` - defines the query to throw an exception in case the - _conditionals_ do not evaluate to *successful*. +- `orThrow(..)` - defines the query to throw an exception in case the + _conditionals_ do not evaluate to _successful_. + +::: warning Careful +Note the difference in _call-time_ and _response-time_ - since the call to the +_terminal_ method is a blocking call, any fetched results as defaults are +prepared as the query is built. In order to dynamically provide better defaults +at _run-time_, use the lazy supplier instead. +::: ### _Informal_ methods @@ -146,39 +149,41 @@ logging or information capture, in case the query fails or an exception is thrown. ```java - public ChainingQueryBuilder onError(Consumer handler) +public ChainingQueryBuilder onError(Consumer handler) ``` -TIP: Try to think more about how the `QueryBuilder` API covers the exceptional - query-cases, as part of the composition of _conditionals_. If clients try - to use _terminals_ that provide sensible defaults, it may not be necessary - to build other types of complex recovery or retries. +::: tip +Try to think more about how the `QueryBuilder` API covers the exceptional +query-cases, as part of the composition of _conditionals_. If clients try +to use _terminals_ that provide sensible defaults, it may not be necessary +to build other types of complex recovery or retries. +::: ### `QueryBuilder` examples Below are some examples of how the different `QueryBuilder` API methods can be combined. -Using `takingAtMost(..)`, combined with `waitingFor(..)`, system resources may -be preserved and the client can be protected from consuming too much data. +Using `takingAtMost(..)`, combined with `waitingFor(..)`, preserves system +resources and the client can be protected from consuming too much data. ```java - return queryBuilder.queryFor("authors", String.class) - .takingAtMost(10) - .waitingFor(800) - .orDefaults(Authors.defaults()); +return queryBuilder.queryFor("authors", String.class) + .takingAtMost(10) + .waitingFor(800) + .orDefaults(Authors.defaults()); ``` It is possible to express constraints at the integration point, also when using -{qr-name}, throwing on an unfulfilled query, as an option to more lenient +{{attr.BRAND}}, throwing on an unfulfilled query, as an option to more lenient handling with defaults. ```java - return queryBuilder.queryFor("offers/rental", Offer.class) - .takingAtLeast(10) - .takingAtMost(20) - .waitingFor(2, ChronoUnit.SECONDS) - .orThrow(TooFewOffersConstraintException::new); +return queryBuilder.queryFor("offers/rental", Offer.class) + .takingAtLeast(10) + .takingAtMost(20) + .waitingFor(2, ChronoUnit.SECONDS) + .orThrow(TooFewOffersConstraintException::new); ``` The _informal_ builder feature, allows for transparency into queries that may @@ -194,7 +199,7 @@ have to be observed. ## `ResponseBuilder` -Another entry-point into {qr-name} is the `ResponseBuilder`. It provides a +Another entry-point into {{attr.BRAND}} is the `ResponseBuilder`. It provides a fluid builder-API that allows users to create responding services or components. It is also provided as a bean, when using the `@EnableQueryResponse` annotation @@ -205,35 +210,36 @@ The `respondWithAuthors()` method below, shows how the injected builder is used to create a responding service. It is invoked by the Spring application context, on the `ApplicationReadyEvent` event. -[source,java] ----- -include::{examples-src}/responding/src/main/java/examples/OnlyThreeAuthors.java[tags=class] ----- +<<<@../../examples/responding/src/main/java/examples/OnlyThreeAuthors.java#class{java} In the example above the responding service is defined by calling the builder method `respondTo(..)` with the query **term** parameter `"authors"`. It will be bound to publish the given 3 authors as `String.class` entries, whenever it consumes a query for the matching string **term** `"authors"`. -This is the most basic premiss of {qr-pfx}, that any string or text term may be -interpreted as a query - it is however up to the response publisher to +This is the most basic premiss of Query/Response, that any string or text term +may be interpreted as a query - it is however up to the response publisher to determine what the query means. -TIP: We've tried to provide information around the {qr-pfx} _protocol_ and - philosophy in the later chapter on <>. Go - there to find out more. +::: tip +We've tried to provide information around the Query/Response _protocol_ and +philosophy in the later chapter on [The Query/Response Protocol](./the-query-response-protocol.md). +Go there to find out more. +::: The second parameter is the the type of each element, that will be published in the response. It is given both as a type hint for the compiler, as well as a parameter to the data mapper. Here it's trivial, the three authors are given as `String.class` entries. -NOTE: The data mapper mentioned above, is in fact the - `com.fasterxml.jackson.databind.ObjectMapper` and {qr-name} currently uses - JSON as the transport format. This means that type hints, JSON mapping - configuration annotations or custom mappings will apply. However as data - mapping on the consumer side is done by coercion, the published format - must conform to some agreed upon standard, shape or protocol. +::: info Note +The data mapper mentioned above, is in fact the +`com.fasterxml.jackson.databind.ObjectMapper` and {{attr.BRAND}} currently uses +JSON as the transport format. This means that type hints, JSON mapping +configuration annotations or custom mappings will apply. However as data +mapping on the consumer side is done by coercion, the published format +must conform to some agreed upon standard, shape or protocol. +::: Response publishers are built using the `respondTo(..)` _initial_ method. Any following call to one of the _terminal_ methods `from(..)` or `suppliedBy(..)` @@ -248,13 +254,13 @@ The table below shows a summary of the builder methods and types. [cols="1,1,3"] .`ResponseBuilder` fluid API method types |=== -| Method | Type | Description +| Method | Type | Description -| `respondTo(..)` | _initial_ | Creates a new builder for a query -| `withAll()` | _batching_ | Specifies NO batches +| `respondTo(..)` | _initial_ | Creates a new builder for a query +| `withAll()` | _batching_ | Specifies NO batches | `withBatchesOf(..)` | _batching_ | Sets the batch size of responses -| `from(..)` | _terminal_ | Terminates with some given response data -| `suppliedBy(..)` | _terminal_ | Terminates with some supplied response data +| `from(..)` | _terminal_ | Terminates with some given response data +| `suppliedBy(..)` | _terminal_ | Terminates with some supplied response data |=== Let's take a closer look at each of the builder method types. @@ -277,10 +283,10 @@ to publish elements of some **type** given as a `Class`. The returned Control over how response elements are published can be made by using the _batching_ methods that the builder provides. -* `withAll()` - defines that **no** batching should be used, and will publish +- `withAll()` - defines that **no** batching should be used, and will publish all given elements, or try to drain a supplied `Iterator` all at once. -* `withBatchesOf(int size)` - defines a batch size, which the response publisher +- `withBatchesOf(int size)` - defines a batch size, which the response publisher will use, to create a series of response messages, with up-to the given `size` of elements. @@ -291,20 +297,20 @@ ensure that a responder is created and added as a query-consumer, a subscriber to the query **term** as a topic. It is not attached to the calling thread, so the builder call always returns after the _terminal_ call. -* `from(..)` - declares the source for the provided response data elements. It +- `from(..)` - declares the source for the provided response data elements. It is declared in a few different ways, for alternative use: ** `from(T... elements)` - vararg elements ** `from(Collection elements)` - provided collection at _build-time_ -** `from(Supplier> elements)` - supplied iterator at _build-time_ +\*\* `from(Supplier> elements)` - supplied iterator at _build-time_ -* `suppliedBy(Supplier> elements)` - declares that response data +- `suppliedBy(Supplier> elements)` - declares that response data is supplied at _run-time_. ### `ResponseBuilder` examples Batch responses provide developers with more options to tune and throttle a -system using {qr-pfx} across many services. It may tune and change the profile +system using Query/Response across many services. It may tune and change the profile of resource use, in a network. ```java From 16ef8c5e3db2eac6c351ed051aa56fc1c48601d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Wed, 10 Jul 2024 19:06:52 +0200 Subject: [PATCH 10/12] Add dev reference --- xdocs/.vitepress/config.mjs | 6 ++- xdocs/.vitepress/theme/custom.css | 7 ---- xdocs/.vitepress/theme/index.js | 5 --- xdocs/reference/developers-reference.md | 49 ++++++++++++------------- 4 files changed, 28 insertions(+), 39 deletions(-) delete mode 100644 xdocs/.vitepress/theme/custom.css delete mode 100644 xdocs/.vitepress/theme/index.js diff --git a/xdocs/.vitepress/config.mjs b/xdocs/.vitepress/config.mjs index 53cceb3..20c49a3 100644 --- a/xdocs/.vitepress/config.mjs +++ b/xdocs/.vitepress/config.mjs @@ -8,7 +8,11 @@ export default defineConfig({ search: { provider: "local", }, - nav: [{ text: "Home", link: "/" }], + nav: [ + { text: "Home", link: "/" }, + { text: "Guide", link: "/guide/what-is-query-response" }, + { text: "Reference", link: "/reference/developers-reference" }, + ], sidebar: [ { text: "Guide", diff --git a/xdocs/.vitepress/theme/custom.css b/xdocs/.vitepress/theme/custom.css deleted file mode 100644 index 1b8e53a..0000000 --- a/xdocs/.vitepress/theme/custom.css +++ /dev/null @@ -1,7 +0,0 @@ -:root { - font-size: 130%; - - --vp-c-brand-1: var(--color-petrol-800); - --vp-c-brand-2: var(--color-petrol-700); - --vp-c-brand-3: var(--color-petrol-600); -} \ No newline at end of file diff --git a/xdocs/.vitepress/theme/index.js b/xdocs/.vitepress/theme/index.js deleted file mode 100644 index f0ac6e5..0000000 --- a/xdocs/.vitepress/theme/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import DefaultTheme from "vitepress/theme"; -import "../../../ui-frontend/src/vars.css"; -import "./custom.css"; - -export default DefaultTheme; diff --git a/xdocs/reference/developers-reference.md b/xdocs/reference/developers-reference.md index 74e6f57..a2e1c2a 100644 --- a/xdocs/reference/developers-reference.md +++ b/xdocs/reference/developers-reference.md @@ -63,7 +63,6 @@ composition of _initial_, _conditional_, an optional _informal_ and exactly one _terminal_ method call. In the table below is a short review of the different builder methods and their types. - ### `QueryBuilder` fluid API method types | Method | Type | Description | @@ -190,11 +189,11 @@ The _informal_ builder feature, allows for transparency into queries that may have to be observed. ```java - return queryBuilder.queryFor("offers/rental", NewOffer.class) - .takingAtLeast(3) - .waitingFor(400) - .onError(error -> LOG.error("Failure!", error)) - .orThrow(TooFewOffersConstraintException::new); +return queryBuilder.queryFor("offers/rental", NewOffer.class) + .takingAtLeast(3) + .waitingFor(400) + .onError(error -> LOG.error("Failure!", error)) + .orThrow(TooFewOffersConstraintException::new); ``` ## `ResponseBuilder` @@ -251,17 +250,15 @@ _batching_, which can be used to control the transfer of data to some degree. The table below shows a summary of the builder methods and types. -[cols="1,1,3"] -.`ResponseBuilder` fluid API method types -|=== -| Method | Type | Description +### `ResponseBuilder` fluid API method types -| `respondTo(..)` | _initial_ | Creates a new builder for a query -| `withAll()` | _batching_ | Specifies NO batches -| `withBatchesOf(..)` | _batching_ | Sets the batch size of responses -| `from(..)` | _terminal_ | Terminates with some given response data -| `suppliedBy(..)` | _terminal_ | Terminates with some supplied response data -|=== +| Method | Type | Description | +| ------------------- | ---------- | ------------------------------------------- | +| `respondTo(..)` | _initial_ | Creates a new builder for a query | +| `withAll()` | _batching_ | Specifies NO batches | +| `withBatchesOf(..)` | _batching_ | Sets the batch size of responses | +| `from(..)` | _terminal_ | Terminates with some given response data | +| `suppliedBy(..)` | _terminal_ | Terminates with some supplied response data | Let's take a closer look at each of the builder method types. @@ -271,7 +268,7 @@ At the moment there's only one _initial_ method for building responses. It is declared as: ```java - public ChainingResponseBuilder respondTo(String term, Class type) +public ChainingResponseBuilder respondTo(String term, Class type) ``` So we can create a response for any `String` **term** and declare that we intend @@ -300,9 +297,9 @@ the builder call always returns after the _terminal_ call. - `from(..)` - declares the source for the provided response data elements. It is declared in a few different ways, for alternative use: -** `from(T... elements)` - vararg elements -** `from(Collection elements)` - provided collection at _build-time_ -\*\* `from(Supplier> elements)` - supplied iterator at _build-time_ + - `from(T... elements)` - vararg elements + - `from(Collection elements)` - provided collection at _build-time_ + - `from(Supplier> elements)` - supplied iterator at _build-time_ - `suppliedBy(Supplier> elements)` - declares that response data is supplied at _run-time_. @@ -314,16 +311,16 @@ system using Query/Response across many services. It may tune and change the pro of resource use, in a network. ```java - responseBuilder.respondTo("offers/monday", Offer.class) - .withBatchesOf(20) - .from(offers.findAllOffersByDayOfWeek(Calendar.MONDAY)); +responseBuilder.respondTo("offers/monday", Offer.class) + .withBatchesOf(20) + .from(offers.findAllOffersByDayOfWeek(Calendar.MONDAY)); ``` Dynamic responses are easy to build, with an API that suits modern Java, using lazy calls to suppliers of data. ```java - responseBuilder.respondTo("users/current", Token.class) - .withBatchesOf(128) - .suppliedBy(userTokenService::findAllCurrentUserTokens); +responseBuilder.respondTo("users/current", Token.class) + .withBatchesOf(128) + .suppliedBy(userTokenService::findAllCurrentUserTokens); ``` From 04e9132df39bea97d6912b0cdea8744ce2b2ba8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Wed, 10 Jul 2024 19:25:29 +0200 Subject: [PATCH 11/12] Ignore docs build dir --- .gitignore | 1 + xdocs/.vitepress/config.mjs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 45d38d5..0ab3963 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ target/ node_modules/ .factorypath +**/.vitepress/dist **/.vitepress/cache diff --git a/xdocs/.vitepress/config.mjs b/xdocs/.vitepress/config.mjs index 20c49a3..f118ae0 100644 --- a/xdocs/.vitepress/config.mjs +++ b/xdocs/.vitepress/config.mjs @@ -4,6 +4,8 @@ export default defineConfig({ title: "Query/Response", description: "A VitePress Site", lastUpdated: true, + footer: true, + ignoreDeadLinks: true, themeConfig: { search: { provider: "local", From 85f5ce5a297475675368daf7251093e406f75e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olle=20T=C3=B6rnstr=C3=B6m?= Date: Wed, 10 Jul 2024 19:33:32 +0200 Subject: [PATCH 12/12] Update make docs target --- Makefile | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 412f898..bd93cdd 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,9 @@ install: .PHONY: docs watch-docs docs: - mvn asciidoctor:process-asciidoc + npm run docs:build mvn javadoc:javadoc - cp -R target/site/apidocs target/generated-docs/ + cp -R target/site/apidocs xdocs/.vitepress/dist/ npm run deploy-docs watch-docs: diff --git a/package.json b/package.json index 598e599..a910434 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "vitepress": "^1.2.3" }, "scripts": { - "deploy-docs": "gh-pages -d target/generated-docs", + "deploy-docs": "gh-pages -d xdocs/.vitepress/dist", "docs:dev": "vitepress dev xdocs", "docs:build": "vitepress build xdocs", "docs:preview": "vitepress preview xdocs"