diff --git a/package-lock.json b/package-lock.json index 183ec755d3..4415a0e279 100644 --- a/package-lock.json +++ b/package-lock.json @@ -955,6 +955,43 @@ "npm-package-arg": "^8.1.0", "npm-registry-fetch": "^9.0.0", "npmlog": "^4.1.2" + }, + "dependencies": { + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "npm-registry-fetch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz", + "integrity": "sha512-PuFYYtnQ8IyVl6ib9d3PepeehcUeHN9IO5N/iCRhyg9tStQcqGQBRVHmfmMWPDERU3KwZoHFvbJ4FPXPspvzbA==", + "dev": true, + "requires": { + "@npmcli/ci-detect": "^1.0.0", + "lru-cache": "^6.0.0", + "make-fetch-happen": "^8.0.9", + "minipass": "^3.1.3", + "minipass-fetch": "^1.3.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.0.0", + "npm-package-arg": "^8.0.0" + } + } } }, "@lerna/npm-install": { @@ -1319,6 +1356,43 @@ "p-pipe": "^3.1.0", "pacote": "^11.2.6", "semver": "^7.3.4" + }, + "dependencies": { + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "npm-registry-fetch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz", + "integrity": "sha512-PuFYYtnQ8IyVl6ib9d3PepeehcUeHN9IO5N/iCRhyg9tStQcqGQBRVHmfmMWPDERU3KwZoHFvbJ4FPXPspvzbA==", + "dev": true, + "requires": { + "@npmcli/ci-detect": "^1.0.0", + "lru-cache": "^6.0.0", + "make-fetch-happen": "^8.0.9", + "minipass": "^3.1.3", + "minipass-fetch": "^1.3.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.0.0", + "npm-package-arg": "^8.0.0" + } + } } }, "@lerna/pulse-till-done": { @@ -1645,9 +1719,9 @@ } }, "@npmcli/run-script": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.4.tgz", - "integrity": "sha512-Yd9HXTtF1JGDXZw0+SOn+mWLYS0e7bHBHVC/2C8yqs4wUrs/k8rwBSinD7rfk+3WG/MFGRZKxjyoD34Pch2E/A==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.5.tgz", + "integrity": "sha512-NQspusBCpTjNwNRFMtz2C5MxoxyzlbuJ4YEhxAKrIonTiirKDtatsZictx9RgamQIx6+QuHMNmPl0wQdoESs9A==", "dev": true, "requires": { "@npmcli/node-gyp": "^1.0.2", @@ -1795,9 +1869,9 @@ } }, "@octokit/openapi-types": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.0.0.tgz", - "integrity": "sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.1.1.tgz", + "integrity": "sha512-ICBhnEb+ahi/TTdNuYb/kTyKVBgAM0VD4k6JPzlhJyzt3Z+Tq/bynwCD+gpkJP7AEcNnzC8YO5R39trmzEo2UA==", "dev": true }, "@octokit/plugin-enterprise-rest": { @@ -1822,12 +1896,12 @@ "dev": true }, "@octokit/plugin-rest-endpoint-methods": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz", - "integrity": "sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.1.tgz", + "integrity": "sha512-vvWbPtPqLyIzJ7A4IPdTl+8IeuKAwMJ4LjvmqWOOdfSuqWQYZXq2CEd0hsnkidff2YfKlguzujHs/reBdAx8Sg==", "dev": true, "requires": { - "@octokit/types": "^6.13.0", + "@octokit/types": "^6.13.1", "deprecation": "^2.3.1" } }, @@ -1865,24 +1939,24 @@ } }, "@octokit/rest": { - "version": "18.5.2", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.2.tgz", - "integrity": "sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw==", + "version": "18.5.3", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.3.tgz", + "integrity": "sha512-KPAsUCr1DOdLVbZJgGNuE/QVLWEaVBpFQwDAz/2Cnya6uW2wJ/P5RVGk0itx7yyN1aGa8uXm2pri4umEqG1JBA==", "dev": true, "requires": { "@octokit/core": "^3.2.3", "@octokit/plugin-paginate-rest": "^2.6.2", "@octokit/plugin-request-log": "^1.0.2", - "@octokit/plugin-rest-endpoint-methods": "5.0.0" + "@octokit/plugin-rest-endpoint-methods": "5.0.1" } }, "@octokit/types": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz", - "integrity": "sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.2.tgz", + "integrity": "sha512-jN5LImYHvv7W6SZargq1UMJ3EiaqIz5qkpfsv4GAb4b16SGqctxtOU2TQAZxvsKHkOw2A4zxdsi5wR9en1/ezQ==", "dev": true, "requires": { - "@octokit/openapi-types": "^6.0.0" + "@octokit/openapi-types": "^6.1.1" } }, "@sindresorhus/is": { @@ -2404,14 +2478,14 @@ } }, "browserslist": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz", - "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==", + "version": "4.16.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.5.tgz", + "integrity": "sha512-C2HAjrM1AI/djrpAUU/tr4pml1DqLIzJKSLDBXBrNErl9ZCCTXdhwxdJjYc16953+mBWf7Lw+uUJgpgb8cN71A==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001208", + "caniuse-lite": "^1.0.30001214", "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.712", + "electron-to-chromium": "^1.3.719", "escalade": "^3.1.1", "node-releases": "^1.1.71" } @@ -2646,9 +2720,9 @@ "dev": true }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -3520,9 +3594,9 @@ } }, "electron-to-chromium": { - "version": "1.3.717", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz", - "integrity": "sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ==", + "version": "1.3.720", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.720.tgz", + "integrity": "sha512-B6zLTxxaOFP4WZm6DrvgRk8kLFYWNhQ5TrHMC0l5WtkMXhU5UbnvWoTfeEwqOruUSlNMhVLfYak7REX6oC5Yfw==", "dev": true }, "emoji-regex": { @@ -3646,9 +3720,9 @@ "dev": true }, "eslint": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.24.0.tgz", - "integrity": "sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.25.0.tgz", + "integrity": "sha512-TVpSovpvCNpLURIScDRB6g5CYu/ZFq9GfX2hLNIV4dSBKxIWojeDODvYl3t0k0VtMxYeR8OXPCFE5+oHMlGfhw==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", @@ -3801,9 +3875,9 @@ } }, "eslint-plugin-jsdoc": { - "version": "32.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-32.3.0.tgz", - "integrity": "sha512-zyx7kajDK+tqS1bHuY5sapkad8P8KT0vdd/lE55j47VPG2MeenSYuIY/M/Pvmzq5g0+3JB+P3BJGUXmHxtuKPQ==", + "version": "32.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-32.3.1.tgz", + "integrity": "sha512-Xcbc8Cr79QveH+MndVwtZ3uafDdXyrsIkS8lP71Fhn5Qi1Lr8TU2QZNkMYIJSvmuLqDB5Jj/VVULMCvaBZBkvg==", "dev": true, "requires": { "comment-parser": "1.1.2", @@ -5129,9 +5203,9 @@ } }, "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.3.0.tgz", + "integrity": "sha512-xSphU2KG9867tsYdLD4RWQ1VqdFl4HTO9Thf3I/3dLEfr0dbPTWKsuCKrgqMljg4nPE+Gq0VCnzT3gr0CyBmsw==", "dev": true, "requires": { "has": "^1.0.3" @@ -5675,15 +5749,15 @@ } }, "libnpmaccess": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-4.0.1.tgz", - "integrity": "sha512-ZiAgvfUbvmkHoMTzdwmNWCrQRsDkOC+aM5BDfO0C9aOSwF3R1LdFDBD+Rer1KWtsoQYO35nXgmMR7OUHpDRxyA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-4.0.2.tgz", + "integrity": "sha512-avXtJibZuGap0/qADDYqb9zdpgzVu/yG5+tl2sTRa7MCkDNv2ZlGwCYI0r6/+tmqXPj0iB9fKexHz426vB326w==", "dev": true, "requires": { "aproba": "^2.0.0", "minipass": "^3.1.1", - "npm-package-arg": "^8.0.0", - "npm-registry-fetch": "^9.0.0" + "npm-package-arg": "^8.1.2", + "npm-registry-fetch": "^10.0.0" }, "dependencies": { "aproba": { @@ -5760,16 +5834,16 @@ } }, "libnpmpublish": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-4.0.0.tgz", - "integrity": "sha512-2RwYXRfZAB1x/9udKpZmqEzSqNd7ouBRU52jyG14/xG8EF+O9A62d7/XVR3iABEQHf1iYhkm0Oq9iXjrL3tsXA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-4.0.1.tgz", + "integrity": "sha512-hZCrZ8v4G9YH3DxpIyBdob25ijD5v5LNzRbwsej4pPDopjdcLLj1Widl+BUeFa7D0ble1JYL4F3owjLJqiA8yA==", "dev": true, "requires": { - "normalize-package-data": "^3.0.0", - "npm-package-arg": "^8.1.0", - "npm-registry-fetch": "^9.0.0", + "normalize-package-data": "^3.0.2", + "npm-package-arg": "^8.1.2", + "npm-registry-fetch": "^10.0.0", "semver": "^7.1.3", - "ssri": "^8.0.0" + "ssri": "^8.0.1" }, "dependencies": { "hosted-git-info": { @@ -6602,21 +6676,21 @@ "dev": true }, "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", "dev": true, "requires": { "npm-normalize-package-bin": "^1.0.1" } }, "npm-check-updates": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-11.5.0.tgz", - "integrity": "sha512-Cv+MOGYGEI3PuhmGc+1gDkIe3275/yLSV0HDB+5YNFnNd5knM71P+ga1TDsRNkp3XHSguB0dCtH2Gx5cKIF5TQ==", + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-11.5.1.tgz", + "integrity": "sha512-4b12O2ioGKbS/4a3i/QfHNIMkNEEq7LtngUSFPatJ3FURIjGT13N/glKO/g2tPmuOtuaTXCnTJlyWBLnf+A//g==", "dev": true, "requires": { - "chalk": "^4.1.0", + "chalk": "^4.1.1", "cint": "^8.2.1", "cli-table": "^0.3.6", "commander": "^6.2.1", @@ -6629,7 +6703,7 @@ "jsonlines": "^0.1.1", "libnpmconfig": "^1.2.1", "lodash": "^4.17.21", - "mem": "^8.1.0", + "mem": "^8.1.1", "minimatch": "^3.0.4", "p-map": "^4.0.0", "pacote": "^11.3.1", @@ -6794,12 +6868,11 @@ } }, "npm-registry-fetch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz", - "integrity": "sha512-PuFYYtnQ8IyVl6ib9d3PepeehcUeHN9IO5N/iCRhyg9tStQcqGQBRVHmfmMWPDERU3KwZoHFvbJ4FPXPspvzbA==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-10.1.1.tgz", + "integrity": "sha512-F6a3l+ffCQ7hvvN16YG5bpm1rPZntCg66PLHDQ1apWJPOCUVHoKnL2w5fqEaTVhp42dmossTyXeR7hTGirfXrg==", "dev": true, "requires": { - "@npmcli/ci-detect": "^1.0.0", "lru-cache": "^6.0.0", "make-fetch-happen": "^8.0.9", "minipass": "^3.1.3", @@ -7297,9 +7370,9 @@ } }, "pacote": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.3.1.tgz", - "integrity": "sha512-TymtwoAG12cczsJIrwI/euOQKtjrQHlD0k0oyt9QSmZGpqa+KdlxKdWR/YUjYizkixaVyztxt/Wsfo8bL3A6Fg==", + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.3.3.tgz", + "integrity": "sha512-GQxBX+UcVZrrJRYMK2HoG+gPeSUX/rQhnbPkkGrCYa4n2F/bgClFPaMm0nsdnYrxnmUy85uMHoFXZ0jTD0drew==", "dev": true, "requires": { "@npmcli/git": "^2.0.1", @@ -7315,7 +7388,7 @@ "npm-package-arg": "^8.0.1", "npm-packlist": "^2.1.4", "npm-pick-manifest": "^6.0.0", - "npm-registry-fetch": "^9.0.0", + "npm-registry-fetch": "^10.0.0", "promise-retry": "^2.0.1", "read-package-json-fast": "^2.0.1", "rimraf": "^3.0.2", @@ -8495,20 +8568,18 @@ } }, "table": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.3.2.tgz", - "integrity": "sha512-I9/Ca6Huf2oxFag7crD0DhA+arIdfLtWunSn0NIXSzjtUlDgIBGVZY7SsMkNPNT3Psd/z4gza0nuEpmra9eRbg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.5.1.tgz", + "integrity": "sha512-xGDXWTBJxahkzPQCsn1S9ESHEenU7TbMD5Iv4FeopXv/XwJyWatFjfbor+6ipI10/MNPXBYUamYukOrbPZ9L/w==", "dev": true, "requires": { "ajv": "^8.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", "lodash.clonedeep": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", - "string-width": "^4.2.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" }, "dependencies": { "ajv": { diff --git a/package.json b/package.json index d46f09ab2d..5a0d778c3a 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,12 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^4.22.0", "@typescript-eslint/parser": "^4.22.0", - "eslint": "^7.24.0", + "eslint": "^7.25.0", "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jsdoc": "^32.3.0", + "eslint-plugin-jsdoc": "^32.3.1", "eslint-plugin-prefer-arrow": "^1.2.3", "lerna": "^4.0.0", - "npm-check-updates": "^11.5.0", + "npm-check-updates": "^11.5.1", "nyc": "^15.1.0", "typescript": "^4.2.4" } diff --git a/packages/adapter-memory/package.json b/packages/adapter-memory/package.json index 5f7bb18970..99363cd3d6 100644 --- a/packages/adapter-memory/package.json +++ b/packages/adapter-memory/package.json @@ -49,7 +49,7 @@ "@feathersjs/adapter-commons": "^5.0.0-pre.3", "@feathersjs/commons": "^5.0.0-pre.3", "@feathersjs/errors": "^5.0.0-pre.3", - "sift": "^13.5.0" + "sift": "^13.5.2" }, "devDependencies": { "@feathersjs/adapter-tests": "^5.0.0-pre.3", diff --git a/packages/client/package.json b/packages/client/package.json index 8b15e4668e..187c3ce7ae 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -68,9 +68,9 @@ "shx": "^0.3.3", "socket.io-client": "^4.0.1", "superagent": "^6.1.0", - "ts-loader": "^9.0.2", + "ts-loader": "^9.1.1", "typescript": "^4.2.4", - "webpack": "^5.34.0", + "webpack": "^5.35.1", "webpack-cli": "^4.6.0", "webpack-merge": "^5.7.3" }, diff --git a/packages/express/package.json b/packages/express/package.json index d08170587b..0c8f0e383c 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -51,6 +51,7 @@ "dependencies": { "@feathersjs/commons": "^5.0.0-pre.3", "@feathersjs/errors": "^5.0.0-pre.3", + "@feathersjs/transport-commons": "^5.0.0-pre.3", "@types/express": "^4.17.11", "express": "^4.17.1", "lodash": "^4.17.21" diff --git a/packages/express/src/rest.ts b/packages/express/src/rest.ts index 1ddcdbb9aa..7876a7a491 100644 --- a/packages/express/src/rest.ts +++ b/packages/express/src/rest.ts @@ -1,29 +1,15 @@ import { MethodNotAllowed } from '@feathersjs/errors'; -import { BaseHookContext, HookContext } from '@feathersjs/hooks'; +import { HookContext } from '@feathersjs/hooks'; import { createDebug } from '@feathersjs/commons'; -import { createContext, defaultServiceMethods, getServiceOptions, NullableId, Params } from '@feathersjs/feathers'; +import { http } from '@feathersjs/transport-commons'; +import { createContext, defaultServiceMethods, getServiceOptions } from '@feathersjs/feathers'; import { Request, Response, NextFunction, RequestHandler, Router } from 'express'; import { parseAuthentication } from './authentication'; const debug = createDebug('@feathersjs/express/rest'); -export const METHOD_HEADER = 'x-service-method'; - -export interface ServiceParams { - id: NullableId, - data: any, - params: Params -} - -export type ServiceCallback = (req: Request, res: Response, options: ServiceParams) => Promise; - -export const statusCodes = { - created: 201, - noContent: 204, - methodNotAllowed: 405, - success: 200 -}; +export type ServiceCallback = (req: Request, res: Response, options: http.ServiceParams) => Promise; export const feathersParams = (req: Request, _res: Response, next: NextFunction) => { req.feathers = { @@ -46,33 +32,6 @@ export const formatter = (_req: Request, res: Response, next: NextFunction) => { }); } -const getData = (context: HookContext) => { - if (!(context instanceof BaseHookContext)) { - return context; - } - - return context.dispatch !== undefined - ? context.dispatch - : context.result; -} - -const getStatusCode = (context: HookContext, res: Response) => { - if (context instanceof BaseHookContext) { - if (context.statusCode) { - return context.statusCode; - } - - if (context.method === 'create') { - return statusCodes.created; - } - } - - if (!res.data) { - return statusCodes.noContent; - } - - return statusCodes.success; -} export const serviceMiddleware = (callback: ServiceCallback) => async (req: Request, res: Response, next: NextFunction) => { @@ -83,9 +42,10 @@ export const serviceMiddleware = (callback: ServiceCallback) => const { __feathersId: id = null, ...route } = req.params; const params = { query, route, ...req.feathers }; const context = await callback(req, res, { id, data, params }); + const result = http.getData(context); - res.data = getData(context); - res.status(getStatusCode(context, res)); + res.data = result; + res.status(http.getStatusCode(context, result)); next(); } catch (error) { @@ -94,14 +54,14 @@ export const serviceMiddleware = (callback: ServiceCallback) => } export const serviceMethodHandler = ( - service: any, methodName: string, getArgs: (opts: ServiceParams) => any[], headerOverride?: string + service: any, methodName: string, getArgs: (opts: http.ServiceParams) => any[], headerOverride?: string ) => serviceMiddleware(async (req, res, options) => { const methodOverride = typeof headerOverride === 'string' && (req.headers[headerOverride] as string); const method = methodOverride ? methodOverride : methodName const { methods } = getServiceOptions(service); if (!methods.includes(method) || defaultServiceMethods.includes(methodOverride)) { - res.status(statusCodes.methodNotAllowed); + res.status(http.statusCodes.methodNotAllowed); throw new MethodNotAllowed(`Method \`${method}\` is not supported by this endpoint.`); } @@ -133,12 +93,12 @@ export function rest (handler: RequestHandler = formatter) { } const baseUri = `/${path}`; - const find = serviceMethodHandler(service, 'find', ({ params }) => [ params ]); - const get = serviceMethodHandler(service, 'get', ({ id, params }) => [ id, params ]); - const create = serviceMethodHandler(service, 'create', ({ data, params }) => [ data, params ], METHOD_HEADER); - const update = serviceMethodHandler(service, 'update', ({ id, data, params }) => [ id, data, params ]); - const patch = serviceMethodHandler(service, 'patch', ({ id, data, params }) => [ id, data, params ]); - const remove = serviceMethodHandler(service, 'remove', ({ id, params }) => [ id, params ]); + const find = serviceMethodHandler(service, 'find', http.argumentsFor.find); + const get = serviceMethodHandler(service, 'get', http.argumentsFor.get); + const create = serviceMethodHandler(service, 'create', http.argumentsFor.create, http.METHOD_HEADER); + const update = serviceMethodHandler(service, 'update', http.argumentsFor.update); + const patch = serviceMethodHandler(service, 'patch', http.argumentsFor.patch); + const remove = serviceMethodHandler(service, 'remove', http.argumentsFor.remove); debug(`Adding REST provider for service \`${path}\` at base route \`${baseUri}\``); diff --git a/packages/express/test/index.test.ts b/packages/express/test/index.test.ts index 9f9467900e..2a9887fcdb 100644 --- a/packages/express/test/index.test.ts +++ b/packages/express/test/index.test.ts @@ -188,6 +188,28 @@ describe('@feathersjs/express', () => { app.use('/myservice', a, b, service, c); }); + it('Express wrapped and context.app are the same', async () => { + const app = expressify.default(feathers()); + + app.use('/test', { + async get (id: Id) { + return { id }; + } + }); + + app.service('test').hooks({ + before: { + get: [context => { + assert.ok(context.app === app); + }] + } + }); + + assert.deepStrictEqual(await app.service('test').get('testing'), { + id: 'testing' + }); + }); + it('Works with HTTPS', done => { const todoService = { async get (name: Id) { diff --git a/packages/koa/LICENSE b/packages/koa/LICENSE new file mode 100644 index 0000000000..6bfc0adefc --- /dev/null +++ b/packages/koa/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2018 Feathers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/packages/koa/README.md b/packages/koa/README.md new file mode 100644 index 0000000000..a1253ff195 --- /dev/null +++ b/packages/koa/README.md @@ -0,0 +1,6 @@ +# @feathersjs/koa + +[![Dependency Status](https://img.shields.io/david/feathersjs/feathers.svg?style=flat-square&path=packages/koa)](https://david-dm.org/feathersjs/feathers?path=packages/koa) +[![Download Status](https://img.shields.io/npm/dm/@feathersjs/koa.svg?style=flat-square)](https://www.npmjs.com/package/@feathersjs/koa) + +> Feathers KoaJS framework bindings and REST provider diff --git a/packages/koa/package.json b/packages/koa/package.json new file mode 100644 index 0000000000..6236a68cff --- /dev/null +++ b/packages/koa/package.json @@ -0,0 +1,72 @@ +{ + "name": "@feathersjs/koa", + "description": "Feathers KoaJS framework bindings and REST provider", + "version": "5.0.0-pre.3", + "homepage": "https://feathersjs.com", + "main": "lib/", + "keywords": [ + "feathers", + "koajs" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/feathersjs/feathers.git" + }, + "author": { + "name": "Feathers contributors", + "email": "hello@feathersjs.com", + "url": "https://feathersjs.com" + }, + "contributors": [], + "bugs": { + "url": "https://github.com/feathersjs/feathers/issues" + }, + "engines": { + "node": ">= 6" + }, + "files": [ + "CHANGELOG.md", + "LICENSE", + "README.md", + "src/**", + "lib/**", + "*.d.ts", + "*.js" + ], + "scripts": { + "prepublish": "npm run compile", + "compile": "shx rm -rf lib/ && tsc", + "test": "npm run compile && npm run mocha", + "mocha": "mocha --config ../../.mocharc.json --recursive test/**.test.ts test/**/*.test.ts" + }, + "directories": { + "lib": "lib" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@feathersjs/authentication": "^5.0.0-pre.3", + "@feathersjs/commons": "^5.0.0-pre.3", + "@feathersjs/errors": "^5.0.0-pre.3", + "@feathersjs/transport-commons": "^5.0.0-pre.3", + "koa": "^2.13.1", + "koa-bodyparser": "^4.3.0" + }, + "devDependencies": { + "@feathersjs/adapter-memory": "^5.0.0-pre.3", + "@feathersjs/authentication-local": "^5.0.0-pre.3", + "@feathersjs/feathers": "^5.0.0-pre.3", + "@feathersjs/tests": "^5.0.0-pre.3", + "@types/debug": "^4.1.5", + "@types/koa": "^2.13.1", + "@types/koa-bodyparser": "^4.3.0", + "@types/mocha": "^8.2.2", + "@types/node": "^14.14.41", + "mocha": "^8.3.2", + "shx": "^0.3.3", + "ts-node": "^9.1.1", + "typescript": "^4.2.4" + } +} diff --git a/packages/koa/src/authenticate.ts b/packages/koa/src/authenticate.ts new file mode 100644 index 0000000000..b68d121251 --- /dev/null +++ b/packages/koa/src/authenticate.ts @@ -0,0 +1,40 @@ +import { Next } from 'koa'; +import { createDebug } from '@feathersjs/commons'; + +import { FeathersKoaContext } from './declarations'; + +const debug = createDebug('@feathersjs/koa:authentication'); + +interface MiddlewareSettings { + service?: string; + strategies?: string[]; +} + +export function authentication (settings: MiddlewareSettings = {}) { + return async (ctx: FeathersKoaContext, next: Next) => { + const { app } = ctx; + const service = app.defaultAuthentication ? app.defaultAuthentication(settings.service) : null; + + if (service === null) { + return next(); + } + + const config = service.configuration; + const authStrategies = settings.strategies || config.parseStrategies || config.authStrategies || []; + + if (authStrategies.length === 0) { + debug('No `authStrategies` or `parseStrategies` found in authentication configuration'); + return next(); + } + + const { req, res } = ctx as any; + const authentication = await service.parse(req, res, ...authStrategies); + + if (authentication) { + debug('Parsed authentication from HTTP header', authentication); + ctx.feathers.authentication = authentication; + } + + return next(); + }; +} diff --git a/packages/koa/src/declarations.ts b/packages/koa/src/declarations.ts new file mode 100644 index 0000000000..e62150d254 --- /dev/null +++ b/packages/koa/src/declarations.ts @@ -0,0 +1,15 @@ +import Koa from 'koa'; +import { Server } from 'http'; +import { Application as FeathersApplication } from '@feathersjs/feathers'; +import '@feathersjs/authentication'; + +export type ApplicationAddons = { + listen (port?: number, ...args: any[]): Promise; +} + +export type Application = + Omit & FeathersApplication & ApplicationAddons; + +export type FeathersKoaContext = Koa.Context & { + app: A; +}; diff --git a/packages/koa/src/error-handler.ts b/packages/koa/src/error-handler.ts new file mode 100644 index 0000000000..754861c95b --- /dev/null +++ b/packages/koa/src/error-handler.ts @@ -0,0 +1,12 @@ +import { FeathersKoaContext } from './declarations'; + +export const errorHandler = () => async (ctx: FeathersKoaContext, next: () => Promise) => { + try { + await next(); + } catch (error) { + ctx.response.status = error.code || 500; + ctx.body = typeof error.toJSON === 'function' ? error.toJSON() : { + message: error.message + }; + } +}; diff --git a/packages/koa/src/index.ts b/packages/koa/src/index.ts new file mode 100644 index 0000000000..2297bcd4ca --- /dev/null +++ b/packages/koa/src/index.ts @@ -0,0 +1,79 @@ +import Debug from 'debug'; +import Koa from 'koa'; +import bodyParser from 'koa-bodyparser'; +import { Application as FeathersApplication } from '@feathersjs/feathers'; +import { routing } from '@feathersjs/transport-commons'; + +import { Application } from './declarations'; +import { errorHandler } from './error-handler'; + +const debug = Debug('@feathersjs/koa'); + +export * from './declarations'; +export * from './authenticate'; +export { rest } from './rest'; +export { Koa, bodyParser, errorHandler }; + +export function koa (_app?: FeathersApplication): Application { + const koaApp = new Koa(); + + if (!_app) { + return koaApp as unknown as Application; + } + + if (typeof _app.setup !== 'function') { + throw new Error('@feathersjs/koa requires a valid Feathers application instance'); + } + + const app = _app as Application; + const { listen: koaListen, use: koaUse } = koaApp; + const oldUse = app.use; + + Object.assign(app, { + use (location: string|Koa.Middleware, ...args: any[]) { + if (typeof location === 'string') { + return (oldUse as any).call(this, location, ...args); + } + + return koaUse.call(this, location); + }, + + async listen (port?: number, ...args: any[]) { + const server = koaListen.call(this, port, ...args); + + await this.setup(server); + debug('Feathers application listening'); + + return server; + } + } as Application); + + const feathersDescriptors = { + ...Object.getOwnPropertyDescriptors(Object.getPrototypeOf(app)), + ...Object.getOwnPropertyDescriptors(app) + }; + const koaDescriptors = { + ...Object.getOwnPropertyDescriptors(Object.getPrototypeOf(koaApp)), + ...Object.getOwnPropertyDescriptors(koaApp) + }; + + // Copy all non-existing properties (including non-enumerables) + // that don't already exist on the Express app + Object.keys(koaDescriptors).forEach(prop => { + const feathersProp = feathersDescriptors[prop]; + const koaProp = koaDescriptors[prop]; + + if (koaProp !== undefined && feathersProp === undefined) { + Object.defineProperty(app, prop, koaProp); + } + }); + + app.configure(routing()); + app.use((ctx, next) => { + ctx.feathers = { provider: 'rest' }; + + return next(); + }); + + return app; +} diff --git a/packages/koa/src/rest.ts b/packages/koa/src/rest.ts new file mode 100644 index 0000000000..045c3b384e --- /dev/null +++ b/packages/koa/src/rest.ts @@ -0,0 +1,51 @@ +import { Next } from 'koa'; +import { http } from '@feathersjs/transport-commons'; +import { createDebug } from '@feathersjs/commons'; +import { getServiceOptions, defaultServiceMethods, createContext } from '@feathersjs/feathers'; +import { MethodNotAllowed } from '@feathersjs/errors'; +import { FeathersKoaContext } from './declarations'; + +const debug = createDebug('@feathersjs/koa:rest'); + +export function rest (){ + return async (ctx: FeathersKoaContext, next: Next) => { + const { app, request } = ctx; + const { query: koaQuery, path, body: data, method: httpMethod } = request; + const query = { ...koaQuery }; + const methodOverride = request.headers[http.METHOD_HEADER] ? + request.headers[http.METHOD_HEADER] as string : null; + const lookup = app.lookup(path); + + if (lookup !== null) { + const { service, params: { __id: id = null, ...route } = {} } = lookup; + const method = http.getServiceMethod(httpMethod, id, methodOverride); + const { methods } = getServiceOptions(service); + + debug(`Found service for path ${path}, attempting to run '${method}' service method`); + + if (!methods.includes(method) || defaultServiceMethods.includes(methodOverride)) { + ctx.response.status = http.statusCodes.methodNotAllowed; + + throw new MethodNotAllowed(`Method \`${method}\` is not supported by this endpoint.`); + } + + const createArguments = (http.argumentsFor as any)[method] || http.argumentsFor.default; + const params = { + ...ctx.feathers, + query, + route + }; + const args = createArguments({ id, data, params }); + const hookContext = createContext(service, method); + + ctx.hook = hookContext as any; + + const result = await (serviceĀ as any)[method](...args, hookContext); + + ctx.response.status = http.getStatusCode(result, {}); + ctx.body = http.getData(result); + } + + return next(); + }; +} diff --git a/packages/koa/test/app.fixture.ts b/packages/koa/test/app.fixture.ts new file mode 100644 index 0000000000..39691ee9c0 --- /dev/null +++ b/packages/koa/test/app.fixture.ts @@ -0,0 +1,67 @@ +import { memory } from '@feathersjs/adapter-memory'; +import { feathers, Params, HookContext } from '@feathersjs/feathers'; +import { authenticate, AuthenticationService, JWTStrategy } from '@feathersjs/authentication'; +import { LocalStrategy, hooks } from '@feathersjs/authentication-local'; + +import { koa, rest, bodyParser, errorHandler, authentication } from '../src'; + +const { protect, hashPassword } = hooks; +const app = koa(feathers()); +const authService = new AuthenticationService(app); + +app.use(errorHandler()); +app.use(authentication()); +app.use(bodyParser()); +app.use(rest()); +app.set('authentication', { + entity: 'user', + service: 'users', + secret: 'supersecret', + authStrategies: [ 'local', 'jwt' ], + parseStrategies: [ 'jwt' ], + local: { + usernameField: 'email', + passwordField: 'password' + } +}); + +authService.register('jwt', new JWTStrategy()); +authService.register('local', new LocalStrategy()); + +app.use('/authentication', authService); +app.use('/users', memory({ + paginate: { + default: 10, + max: 20 + } +})); + +app.service('users').hooks({ + before: { + create: [ + hashPassword('password') + ] + }, + after: { + all: [protect('password')], + get: [(context: HookContext) => { + if (context.params.provider) { + context.result.fromGet = true; + } + + return context; + }] + } +}); + +app.use('/dummy', { + async get (id: string, params: Params) { + return { id, params }; + } +}); + +app.service('dummy').hooks({ + before: [authenticate('jwt')] +}); + +export default app; diff --git a/packages/koa/test/authentication.test.ts b/packages/koa/test/authentication.test.ts new file mode 100644 index 0000000000..62d6defcd8 --- /dev/null +++ b/packages/koa/test/authentication.test.ts @@ -0,0 +1,117 @@ +import { strict as assert } from 'assert'; +import _axios from 'axios'; +import { Server } from 'http'; +import { AuthenticationResult } from '@feathersjs/authentication'; + +import app from './app.fixture'; + +const axios = _axios.create({ + baseURL: 'http://localhost:9776/' +}); + +describe('@feathersjs/koa/authentication', () => { + const email = 'koatest@authentication.com'; + const password = 'superkoa'; + + let server: Server; + let authResult: AuthenticationResult; + let user: any; + + before(async () => { + server = await app.listen(9776); + user = await app.service('users').create({ email, password }); + authResult = (await axios.post('/authentication', { + strategy: 'local', + password, + email + })).data; + }); + + after(done => server.close(done)); + + describe('service authentication', () => { + it('successful local authentication', () => { + assert.ok(authResult.accessToken); + assert.strictEqual(authResult.user.email, email); + assert.strictEqual(authResult.user.password, undefined); + }); + + it('local authentication with wrong password fails', async () => { + try { + await axios.post('/authentication', { + strategy: 'local', + password: 'wrong', + email + }); + assert.fail('Should never get here'); + } catch (error) { + const { data } = error.response; + assert.strictEqual(data.name, 'NotAuthenticated'); + assert.strictEqual(data.message, 'Invalid login'); + } + }); + + it('authenticating with JWT works but returns same accessToken', async () => { + const { accessToken } = authResult; + + const { data } = await axios.post('/authentication', { + strategy: 'jwt', + accessToken + }); + + assert.strictEqual(data.accessToken, accessToken); + assert.strictEqual(data.authentication.strategy, 'jwt'); + assert.strictEqual(data.authentication.payload.sub, user.id.toString()); + assert.strictEqual(data.user.email, email); + }); + + it('can make a protected request with Authorization header', async () => { + const { accessToken } = authResult; + + const { data } = await axios.get('/dummy/dave', { + headers: { + Authorization: accessToken + } + }); + + assert.strictEqual(data.id, 'dave'); + assert.deepStrictEqual(data.params.user, user); + assert.strictEqual(data.params.authentication.accessToken, accessToken); + }); + + it('errors when there are no authStrategies and parseStrategies', async () => { + const { accessToken } = authResult; + + app.get('authentication').authStrategies = []; + delete app.get('authentication').parseStrategies; + + try { + await axios.get('/dummy/dave', { + headers: { + Authorization: accessToken + } + }); + assert.fail('Should never get here'); + } catch (error) { + assert.strictEqual(error.response.data.name, 'NotAuthenticated'); + app.get('authentication').authStrategies = [ 'jwt', 'local' ]; + } + }); + + it('can make a protected request with Authorization header and bearer scheme', () => { + const { accessToken } = authResult; + + return axios.get('/dummy/dave', { + headers: { + Authorization: ` Bearer: ${accessToken}` + } + }).then(res => { + const { data, data: { params } } = res; + + assert.strictEqual(data.id, 'dave'); + assert.deepStrictEqual(params.user, user); + assert.strictEqual(params.authentication.accessToken, accessToken); + }); + }); + }); +}); diff --git a/packages/koa/test/index.test.ts b/packages/koa/test/index.test.ts new file mode 100644 index 0000000000..ada3f585c1 --- /dev/null +++ b/packages/koa/test/index.test.ts @@ -0,0 +1,123 @@ +import { strict as assert } from 'assert'; +import Koa from 'koa'; +import axios from 'axios'; +import { Server } from 'http'; +import { feathers, Id } from '@feathersjs/feathers'; +import { Service, restTests } from '@feathersjs/tests'; +import { koa, rest, Application, bodyParser, errorHandler } from '../src'; + +describe('@feathersjs/koa', () => { + let app: Application; + let server: Server; + + before(async () => { + app = koa(feathers()); + app.use(errorHandler()); + app.use(bodyParser()); + app.use(rest()); + app.use('/', new Service()); + app.use('todo', new Service(), { + methods: [ + 'get', 'find', 'create', 'update', + 'patch', 'remove', 'customMethod' + ] + }); + app.use(ctx => { + if (ctx.request.path === '/middleware') { + ctx.body = { + feathers: ctx.feathers, + message: 'Hello from middleware' + }; + } + }); + + server = await app.listen(8465); + }); + + after(() => server.close()); + + it('throws an error when initialized with invalid application', () => { + try { + koa({} as Application); + assert.fail('Should never get here'); + } catch (error) { + assert.equal(error.message, '@feathersjs/koa requires a valid Feathers application instance'); + } + }); + + it('returns Koa instance when no Feathers app is passed', () => { + assert.ok(koa() instanceof Koa); + }); + + it('Koa wrapped and context.app are the same', async () => { + const app = koa(feathers()); + + app.use('/test', { + async get (id: Id) { + return { id }; + } + }); + + app.service('test').hooks({ + before: { + get: [context => { + assert.ok(context.app === app); + }] + } + }); + + assert.deepStrictEqual(await app.service('test').get('testing'), { + id: 'testing' + }); + }); + + it('starts as a Koa and Feathers application', async () => { + const { data } = await axios.get('http://localhost:8465/middleware'); + const todo = await app.service('todo').get('dishes', { + query: {} + }); + + assert.deepEqual(data, { + message: 'Hello from middleware', + feathers: { + provider: 'rest' + } + }); + assert.deepEqual(todo, { + id: 'dishes', + description: 'You have to do dishes!' + }); + }); + + it('works with custom methods that are allowed', async () => { + const { data } = await axios.post('http://localhost:8465/todo', { + message: 'Custom hello' + }, { + headers: { + 'X-Service-Method': 'customMethod' + } + }); + + assert.deepStrictEqual(data, { + data: { message: 'Custom hello' }, + method: 'customMethod', + provider: 'rest' + }); + + await assert.rejects(() => axios.post('http://localhost:8465/todo', {}, { + headers: { + 'X-Service-Method': 'internalMethod' + } + }), error => { + const { data } = error.response; + + assert.strictEqual(data.code, 405); + assert.strictEqual(data.message, 'Method `internalMethod` is not supported by this endpoint.'); + + return true; + }) + }); + + restTests('Services', 'todo', 8465); + restTests('Root service', '/', 8465); +}); diff --git a/packages/koa/tsconfig.json b/packages/koa/tsconfig.json new file mode 100644 index 0000000000..316fd41336 --- /dev/null +++ b/packages/koa/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig", + "include": [ + "src/**/*.ts" + ], + "compilerOptions": { + "outDir": "lib" + } +} diff --git a/packages/transport-commons/src/http.ts b/packages/transport-commons/src/http.ts new file mode 100644 index 0000000000..aa7d92b18e --- /dev/null +++ b/packages/transport-commons/src/http.ts @@ -0,0 +1,83 @@ +import { MethodNotAllowed } from '@feathersjs/errors/lib'; +import { HookContext, NullableId, Params } from '@feathersjs/feathers'; +import { BaseHookContext } from '@feathersjs/hooks'; + +export const METHOD_HEADER = 'x-service-method'; + +export interface ServiceParams { + id: NullableId, + data: any, + params: Params +} + +export const statusCodes = { + created: 201, + noContent: 204, + methodNotAllowed: 405, + success: 200 +}; + +export const knownMethods: { [key: string]: any } = { + post: 'create', + patch: 'patch', + put: 'update', + delete: 'remove' +}; + +export function getServiceMethod (_httpMethod: string, id: unknown, headerOverride?: string) { + const httpMethod = _httpMethod.toLowerCase(); + + if (httpMethod === 'post' && headerOverride) { + return headerOverride; + } + + const mappedMethod = knownMethods[httpMethod]; + + if (mappedMethod) { + return mappedMethod; + } + + if (httpMethod === 'get') { + return id === null ? 'find' : 'get'; + } + + throw new MethodNotAllowed(`Method ${_httpMethod} not allowed`); +} + +export const argumentsFor = { + get: ({ id, params }: ServiceParams) => [ id, params ], + find: ({ params }: ServiceParams) => [ params ], + create: ({ data, params }: ServiceParams) => [ data, params ], + update: ({ id, data, params }: ServiceParams) => [ id, data, params ], + patch: ({ id, data, params }: ServiceParams) => [ id, data, params ], + remove: ({ id, params }: ServiceParams) => [ id, params ], + default: ({ data, params }: ServiceParams) => [ data, params ] +} + +export function getData (context: HookContext|{ [key: string]: any }) { + if (!(context instanceof BaseHookContext)) { + return context; + } + + return context.dispatch !== undefined + ? context.dispatch + : context.result; +} + +export function getStatusCode (context: HookContext, data?: any) { + if (context instanceof BaseHookContext) { + if (context.statusCode) { + return context.statusCode; + } + + if (context.method === 'create') { + return statusCodes.created; + } + } + + if (!data) { + return statusCodes.noContent; + } + + return statusCodes.success; +} diff --git a/packages/transport-commons/src/index.ts b/packages/transport-commons/src/index.ts index dd159c5134..6db39360b2 100644 --- a/packages/transport-commons/src/index.ts +++ b/packages/transport-commons/src/index.ts @@ -2,4 +2,5 @@ import { socket } from './socket'; import { routing } from './routing'; import { channels } from './channels'; +export * as http from './http'; export { socket, routing, channels }; diff --git a/packages/transport-commons/test/http.test.ts b/packages/transport-commons/test/http.test.ts new file mode 100644 index 0000000000..ea100044a1 --- /dev/null +++ b/packages/transport-commons/test/http.test.ts @@ -0,0 +1,45 @@ +import assert from 'assert'; +import { HookContext } from '@feathersjs/feathers'; +import { BaseHookContext } from '@feathersjs/hooks'; + +import { http } from '../src'; + +describe('@feathersjs/transport-commons HTTP helpers', () => { + it('getData', () => { + const plainData = { message: 'hi' }; + const dispatch = { message: 'from dispatch' }; + const resultContext = new BaseHookContext({ + result: plainData + }); + const dispatchContext = new BaseHookContext({ + dispatch + }); + + assert.deepStrictEqual(http.getData(plainData), plainData); + assert.deepStrictEqual(http.getData(resultContext), plainData); + assert.deepStrictEqual(http.getData(dispatchContext), dispatch); + }); + + it('getStatusCode', async () => { + const statusContext = new BaseHookContext({ + statusCode: 202 + }); + const createContext = new BaseHookContext({ + method: 'create' + }); + + assert.strictEqual(http.getStatusCode(statusContext as HookContext, {}), 202); + assert.strictEqual(http.getStatusCode(createContext as HookContext, {}), http.statusCodes.created); + assert.strictEqual(http.getStatusCode({} as HookContext), http.statusCodes.noContent); + assert.strictEqual(http.getStatusCode({} as HookContext, {}), http.statusCodes.success); + }); + + it('getServiceMethod', () => { + assert.strictEqual(http.getServiceMethod('GET', 2), 'get'); + assert.strictEqual(http.getServiceMethod('GET', null), 'find'); + assert.strictEqual(http.getServiceMethod('PoST', null), 'create'); + assert.strictEqual(http.getServiceMethod('PoST', null, 'customMethod'), 'customMethod'); + assert.strictEqual(http.getServiceMethod('delete', null), 'remove'); + assert.throws(() => http.getServiceMethod('nonsense', null)); + }); +});