diff --git a/example/src/application.ts b/example/src/application.ts index e0a7bba8..88332876 100644 --- a/example/src/application.ts +++ b/example/src/application.ts @@ -1,5 +1,4 @@ import { bootstrap, Get, OpenApiController, Mount, Controller, AppOptions } from '../../dist'; -import { Injectable } from 'injection-js'; @Controller('', { group: 'test' diff --git a/package-lock.json b/package-lock.json index 644c2ec8..7f52e14c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -916,9 +916,9 @@ } }, "@types/node": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.7.1.tgz", - "integrity": "sha512-EGoI4ylB/lPOaqXqtzAyL8HcgOuCtH2hkEaLmkueOYufsTFWBn4VCvlCDC2HW8Q+9iF+QVC3sxjDKQYjHQeZ9w==" + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.9.4.tgz", + "integrity": "sha512-fCHV45gS+m3hH17zgkgADUSi2RR1Vht6wOZ0jyHP8rjiQra9f+mIcgwPQHllmDocYOstIEbKlxbFDYlgrTPYqw==" }, "@types/range-parser": { "version": "1.2.2", diff --git a/packages/annotations/package-lock.json b/packages/annotations/package-lock.json index d42adbc7..2fd9b947 100644 --- a/packages/annotations/package-lock.json +++ b/packages/annotations/package-lock.json @@ -440,9 +440,9 @@ } }, "ci-info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.5.0.tgz", - "integrity": "sha512-Bx/xWOzip4whERIvC97aIHjWCa8FxEn0ezng0oVn4kma6p+90Fbs3bTcJw6ZL0da2EPHydxsXJPZxNUv5oWb1Q==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.5.1.tgz", + "integrity": "sha512-fKFIKXaYiL1exImwJ0AhR/6jxFPSKQBk2ayV5NiNoruUs2+rxC2kNw0EG+1Z9dugZRdCrppskQ8DN2cyaUM1Hw==", "dev": true }, "class-utils": { @@ -692,7 +692,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -709,12 +709,13 @@ "dev": true }, "event-stream": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.5.tgz", - "integrity": "sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz", + "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==", "dev": true, "requires": { "duplexer": "^0.1.1", + "flatmap-stream": "^0.1.0", "from": "^0.1.7", "map-stream": "0.0.7", "pause-stream": "^0.0.11", @@ -897,6 +898,12 @@ } } }, + "flatmap-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.0.tgz", + "integrity": "sha512-Nlic4ZRYxikqnK5rj3YoxDVKGGtUjcNDUtvQ7XsdGLZmMwdUYnXf10o1zcXtzEZTBgc6GxeRpQxV/Wu3WPIIHA==", + "dev": true + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -1698,12 +1705,12 @@ "dev": true }, "is-ci": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.0.tgz", - "integrity": "sha512-plgvKjQtalH2P3Gytb7L61Lmz95g2DlpzFiQyRSFew8WoJKxtKRzrZMeyRN2supblm3Psc8OQGy7Xjb6XG11jw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { - "ci-info": "^1.3.0" + "ci-info": "^1.5.0" } }, "is-data-descriptor": { @@ -2261,7 +2268,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -2373,7 +2380,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -2779,7 +2786,7 @@ }, "stream-combiner": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", "dev": true, "requires": { @@ -2887,7 +2894,7 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, diff --git a/packages/common/package-lock.json b/packages/common/package-lock.json index 4d15c316..b509315c 100644 --- a/packages/common/package-lock.json +++ b/packages/common/package-lock.json @@ -10,27 +10,196 @@ "integrity": "sha512-fCHV45gS+m3hH17zgkgADUSi2RR1Vht6wOZ0jyHP8rjiQra9f+mIcgwPQHllmDocYOstIEbKlxbFDYlgrTPYqw==", "dev": true }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "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": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + }, "colors": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.2.tgz", "integrity": "sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ==" }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, "dotenv": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz", "integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg==" }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "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": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "parent-module": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-0.1.0.tgz", + "integrity": "sha1-tSkoY6HoxHbs+Ffn11yYkgskuKY=", + "requires": { + "callsites": "^1.0.0" + }, + "dependencies": { + "callsites": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-1.0.1.tgz", + "integrity": "sha1-wUwkGIzo4dagMLTDyULmuolbaho=" + } + } + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, "razmin": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/razmin/-/razmin-0.2.0.tgz", - "integrity": "sha512-GvKRA0cpVedlvgz02h1DcOtPXnpvh01zghK/Xy+BIjl4WLBgISfKVAx9SbBU96xa7V6GtKroUN4BONRAFrP4/Q==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/razmin/-/razmin-0.4.0.tgz", + "integrity": "sha512-iP38gEa7v8qhckNEjsxb5Sckf12t0KAt0IQn9dISD5fjTcHlArLI/bfzcaOxM9CYZLbDyZ/CmbFg5CeUBZADWg==", "requires": { + "callsites": "^2.0.0", "colors": "^1.1.2", + "require-glob": "^3.2.0", "rxjs": "^5.5.6", "source-map-support": "^0.5.0", "zone.js": "^0.8.20" @@ -46,6 +215,16 @@ } } }, + "require-glob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/require-glob/-/require-glob-3.2.0.tgz", + "integrity": "sha1-kL/iyO+0ufly65o/XlgIMuBPZNM=", + "requires": { + "glob-parent": "^3.0.0", + "globby": "^6.0.0", + "parent-module": "^0.1.0" + } + }, "rxjs": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz", @@ -78,6 +257,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, "zone.js": { "version": "0.8.26", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.26.tgz", diff --git a/packages/di/package-lock.json b/packages/di/package-lock.json index 6e6d0a89..cfd6b280 100644 --- a/packages/di/package-lock.json +++ b/packages/di/package-lock.json @@ -8,6 +8,12 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/injection-js/-/injection-js-2.2.1.tgz", "integrity": "sha512-zHI+E+dM0PXix5FFTO1Y4/UOyAzE7zG1l/QwAn4jchTThOoBq+UYRFK4AVG7lQgFL+go62SbrzSsjXy9DFEZUg==" + }, + "reflect-metadata": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==", + "dev": true } } } diff --git a/packages/di/src/index.ts b/packages/di/src/index.ts index 52d49094..0db7c95f 100644 --- a/packages/di/src/index.ts +++ b/packages/di/src/index.ts @@ -1,2 +1,13 @@ export * from './metadata'; -export * from './modules'; \ No newline at end of file +export * from './modules'; +export { + Injector, + InjectionToken, + ReflectiveInjector, + Provider, + ClassProvider, + ExistingProvider, + FactoryProvider, + TypeProvider, + ValueProvider +} from 'injection-js'; \ No newline at end of file diff --git a/packages/http/package-lock.json b/packages/http/package-lock.json new file mode 100644 index 00000000..ea52f7ea --- /dev/null +++ b/packages/http/package-lock.json @@ -0,0 +1,277 @@ +{ + "name": "@alterior/http", + "version": "3.0.0-beta.4", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "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": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "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" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "colors": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.2.tgz", + "integrity": "sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "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" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "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": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "parent-module": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-0.1.0.tgz", + "integrity": "sha1-tSkoY6HoxHbs+Ffn11yYkgskuKY=", + "dev": true, + "requires": { + "callsites": "^1.0.0" + }, + "dependencies": { + "callsites": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-1.0.1.tgz", + "integrity": "sha1-wUwkGIzo4dagMLTDyULmuolbaho=", + "dev": true + } + } + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "razmin": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/razmin/-/razmin-0.5.0.tgz", + "integrity": "sha512-W40JFOuquSUpf+fxm+inYZfuV3qR+4iEc64saoOVAyLcjX6CGfSCGtjWBdN3BgJ2LTPzLsAVmMri1fUoJO9koA==", + "dev": true, + "requires": { + "callsites": "^2.0.0", + "colors": "^1.1.2", + "require-glob": "^3.2.0", + "rxjs": "^6.3.2", + "source-map-support": "^0.5.0", + "zone.js": "^0.8.20" + } + }, + "require-glob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/require-glob/-/require-glob-3.2.0.tgz", + "integrity": "sha1-kL/iyO+0ufly65o/XlgIMuBPZNM=", + "dev": true, + "requires": { + "glob-parent": "^3.0.0", + "globby": "^6.0.0", + "parent-module": "^0.1.0" + } + }, + "rxjs": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz", + "integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "zone.js": { + "version": "0.8.26", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.26.tgz", + "integrity": "sha512-W9Nj+UmBJG251wkCacIkETgra4QgBo/vgoEkb4a2uoLzpQG7qF9nzwoLXWU5xj3Fg2mxGvEDh47mg24vXccYjA==", + "dev": true + } + } +} diff --git a/packages/http/package.json b/packages/http/package.json index 06ce196c..68929596 100644 --- a/packages/http/package.json +++ b/packages/http/package.json @@ -11,7 +11,7 @@ "scripts": { "build": "tsc -p .", "prepare": "npm run test", - "test": "exit 0" + "test": "npm run build && node dist/test/test.js" }, "repository": { "type": "git", @@ -25,10 +25,12 @@ "homepage": "https://github.com/alterior-mvc/alterior#readme", "dependencies": { "@alterior/common": "^3.0.0-beta.4", - "@alterior/di": "^3.0.0-beta.4" + "@alterior/di": "^3.0.0-beta.4", + "@alterior/runtime": "^3.0.0-beta.4", + "rxjs": "^6.3.2" }, "gitHead": "dd889c35cf093b43b0156780f242919f41935b55", "devDependencies": { - "razmin": "^0.4.0" + "razmin": "^0.5.0" } } diff --git a/packages/http/src/backend.ts b/packages/http/src/backend.ts new file mode 100644 index 00000000..1c0c84cf --- /dev/null +++ b/packages/http/src/backend.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Observable} from 'rxjs'; +import {HttpRequest} from './request'; +import {HttpEvent} from './response'; + +/** + * Transforms an `HttpRequest` into a stream of `HttpEvent`s, one of which will likely be a + * `HttpResponse`. + * + * `HttpHandler` is injectable. When injected, the handler instance dispatches requests to the + * first interceptor in the chain, which dispatches to the second, etc, eventually reaching the + * `HttpBackend`. + * + * In an `HttpInterceptor`, the `HttpHandler` parameter is the next interceptor in the chain. + * + * + */ +export abstract class HttpHandler { + abstract handle(req: HttpRequest): Observable>; +} + +/** + * A final `HttpHandler` which will dispatch the request via browser HTTP APIs to a backend. + * + * Interceptors sit between the `HttpClient` interface and the `HttpBackend`. + * + * When injected, `HttpBackend` dispatches requests directly to the backend, without going + * through the interceptor chain. + * + * + */ +export abstract class HttpBackend implements HttpHandler { + abstract handle(req: HttpRequest): Observable>; +} diff --git a/packages/http/src/client.ts b/packages/http/src/client.ts new file mode 100644 index 00000000..e269aaeb --- /dev/null +++ b/packages/http/src/client.ts @@ -0,0 +1,1999 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Injectable} from '@alterior/di'; +import {Observable, of } from 'rxjs'; +import {concatMap, filter, map} from 'rxjs/operators'; + +import {HttpHandler} from './backend'; +import {HttpHeaders} from './headers'; +import {HttpParams, HttpParamsOptions} from './params'; +import {HttpRequest} from './request'; +import {HttpEvent, HttpResponse} from './response'; + + +/** + * Construct an instance of `HttpRequestOptions` from a source `HttpMethodOptions` and + * the given `body`. Basically, this clones the object and adds the body. + */ +function addBody( + options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: HttpObserve, + params?: HttpParams | {[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'arraybuffer' | 'blob' | 'json' | 'text', + withCredentials?: boolean, + }, + body: T | null): any { + return { + body, + headers: options.headers, + observe: options.observe, + params: options.params, + reportProgress: options.reportProgress, + responseType: options.responseType, + withCredentials: options.withCredentials, + }; +} + +export type HttpObserve = 'body' | 'events' | 'response'; + +/** + * Perform HTTP requests. + * + * `HttpClient` is available as an injectable class, with methods to perform HTTP requests. + * Each request method has multiple signatures, and the return type varies according to which + * signature is called (mainly the values of `observe` and `responseType`). + * + * + */ +@Injectable() +export class HttpClient { + constructor(private handler: HttpHandler) {} + + /** + * Send the given `HttpRequest` and return a stream of `HttpEvents`. + */ + request(req: HttpRequest): Observable>; + + /** + * Construct a request which interprets the body as an `ArrayBuffer` and returns it. + * + * @return an `Observable` of the body as an `ArrayBuffer`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable; + + /** + * Construct a request which interprets the body as a `Blob` and returns it. + * + * @return an `Observable` of the body as a `Blob`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable; + + /** + * Construct a request which interprets the body as text and returns it. + * + * @return an `Observable` of the body as a `string`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable; + + /** + * Construct a request which interprets the body as an `ArrayBuffer` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + params?: HttpParams|{[param: string]: string | string[]}, + observe: 'events', reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a request which interprets the body as an `Blob` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Blob`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a request which interprets the body as text and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `string`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Object`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + reportProgress?: boolean, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `R`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + reportProgress?: boolean, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a request which interprets the body as an `ArrayBuffer` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `ArrayBuffer`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a request which interprets the body as a `Blob` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Blob`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a request which interprets the body as text and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `string`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Object`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + reportProgress?: boolean, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `R`. + */ + request(method: string, url: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + reportProgress?: boolean, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Object`. + */ + request(method: string, url: string, options?: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + responseType?: 'json', + reportProgress?: boolean, + withCredentials?: boolean, + }): Observable; + + /** + * Construct a request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `R`. + */ + request(method: string, url: string, options?: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + responseType?: 'json', + reportProgress?: boolean, + withCredentials?: boolean, + }): Observable; + + /** + * Construct a request in a manner where response type and requested `Observable` are not known + * statically. + * + * @return an `Observable` of whatever was requested, typed to `any`. + */ + request(method: string, url: string, options?: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + params?: HttpParams|{[param: string]: string | string[]}, + observe?: HttpObserve, + reportProgress?: boolean, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + }): Observable; + + /** + * Constructs an `Observable` for a particular HTTP request that, when subscribed, + * fires the request through the chain of registered interceptors and on to the + * server. + * + * This method can be called in one of two ways. Either an `HttpRequest` + * instance can be passed directly as the only parameter, or a method can be + * passed as the first parameter, a string URL as the second, and an + * options hash as the third. + * + * If a `HttpRequest` object is passed directly, an `Observable` of the + * raw `HttpEvent` stream will be returned. + * + * If a request is instead built by providing a URL, the options object + * determines the return type of `request()`. In addition to configuring + * request parameters such as the outgoing headers and/or the body, the options + * hash specifies two key pieces of information about the request: the + * `responseType` and what to `observe`. + * + * The `responseType` value determines how a successful response body will be + * parsed. If `responseType` is the default `json`, a type interface for the + * resulting object may be passed as a type parameter to `request()`. + * + * The `observe` value determines the return type of `request()`, based on what + * the consumer is interested in observing. A value of `events` will return an + * `Observable` representing the raw `HttpEvent` stream, + * including progress events by default. A value of `response` will return an + * `Observable>` where the `T` parameter of `HttpResponse` + * depends on the `responseType` and any optionally provided type parameter. + * A value of `body` will return an `Observable` with the same `T` body type. + */ + request(first: string|HttpRequest, url?: string, options: { + body?: any, + headers?: HttpHeaders|{[header: string]: string | string[]}, + observe?: HttpObserve, + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + } = {}): Observable { + let req: HttpRequest; + // Firstly, check whether the primary argument is an instance of `HttpRequest`. + if (first instanceof HttpRequest) { + // It is. The other arguments must be undefined (per the signatures) and can be + // ignored. + req = first as HttpRequest; + } else { + // It's a string, so it represents a URL. Construct a request based on it, + // and incorporate the remaining arguments (assuming GET unless a method is + // provided. + + // Figure out the headers. + let headers: HttpHeaders|undefined = undefined; + if (options.headers instanceof HttpHeaders) { + headers = options.headers; + } else { + headers = new HttpHeaders(options.headers); + } + + // Sort out parameters. + let params: HttpParams|undefined = undefined; + if (!!options.params) { + if (options.params instanceof HttpParams) { + params = options.params; + } else { + params = new HttpParams({ fromObject: options.params } as HttpParamsOptions); + } + } + + // Construct the request. + req = new HttpRequest(first, url !, (options.body !== undefined ? options.body : null), { + headers, + params, + reportProgress: options.reportProgress, + // By default, JSON is assumed to be returned for all calls. + responseType: options.responseType || 'json', + withCredentials: options.withCredentials, + }); + } + + // Start with an Observable.of() the initial request, and run the handler (which + // includes all interceptors) inside a concatMap(). This way, the handler runs + // inside an Observable chain, which causes interceptors to be re-run on every + // subscription (this also makes retries re-run the handler, including interceptors). + + const events$: Observable> = of (req).pipe(concatMap((req: HttpRequest) => { + try { + return this.handler.handle(req); + } catch (e) { + console.error(`Error caught while handling HTTP request:`); + console.error(e); + throw e; + } + })); + + + // If coming via the API signature which accepts a previously constructed HttpRequest, + // the only option is to get the event stream. Otherwise, return the event stream if + // that is what was requested. + if (first instanceof HttpRequest || options.observe === 'events') { + return events$; + } + + // The requested stream contains either the full response or the body. In either + // case, the first step is to filter the event stream to extract a stream of + // responses(s). + const res$: Observable> = >>events$.pipe( + filter((event: HttpEvent) => event instanceof HttpResponse)); + + // Decide which stream to return. + switch (options.observe || 'body') { + case 'body': + // The requested stream is the body. Map the response stream to the response + // body. This could be done more simply, but a misbehaving interceptor might + // transform the response body into a different format and ignore the requested + // responseType. Guard against this by validating that the response is of the + // requested type. + switch (req.responseType) { + case 'arraybuffer': + return res$.pipe(map((res: HttpResponse) => { + // Validate that the body is an ArrayBuffer. + if (res.body !== null && !(res.body instanceof ArrayBuffer)) { + throw new Error('Response is not an ArrayBuffer.'); + } + return res.body; + })); + case 'blob': + return res$.pipe(map((res: HttpResponse) => { + // Validate that the body is a Blob. + if (res.body !== null && !(res.body instanceof Blob)) { + throw new Error('Response is not a Blob.'); + } + return res.body; + })); + case 'text': + return res$.pipe(map((res: HttpResponse) => { + // Validate that the body is a string. + if (res.body !== null && typeof res.body !== 'string') { + throw new Error('Response is not a string.'); + } + return res.body; + })); + case 'json': + default: + // No validation needed for JSON responses, as they can be of any type. + return res$.pipe(map((res: HttpResponse) => res.body)); + } + case 'response': + // The response stream was requested directly, so return it. + return res$; + default: + // Guard against new future observe types being added. + throw new Error(`Unreachable: unhandled observe type ${options.observe}}`); + } + } + + /** + * Construct a DELETE request which interprets the body as an `ArrayBuffer` and returns it. + * + * @return an `Observable` of the body as an `ArrayBuffer`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable; + + + /** + * Construct a DELETE request which interprets the body as a `Blob` and returns it. + * + * @return an `Observable` of the body as a `Blob`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable; + + /** + * Construct a DELETE request which interprets the body as text and returns it. + * + * @return an `Observable` of the body as a `string`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable; + + /** + * Construct a DELETE request which interprets the body as an `ArrayBuffer` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a DELETE request which interprets the body as a `Blob` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Blob`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a DELETE request which interprets the body as text and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `string`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a DELETE request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Object`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a DELETE request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `T`. + */ + delete(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a DELETE request which interprets the body as an `ArrayBuffer` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `ArrayBuffer`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a DELETE request which interprets the body as a `Blob` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Blob`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a DELETE request which interprets the body as text and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `string`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a DELETE request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Object`. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a DELETE request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `T`. + */ + delete(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a DELETE request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as an `Object`. + */ + delete (url: string, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Construct a DELETE request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as type `T`. + */ + delete(url: string, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Constructs an `Observable` which, when subscribed, will cause the configured + * DELETE request to be executed on the server. See the individual overloads for + * details of `delete()`'s return type based on the provided options. + */ + delete (url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: HttpObserve, + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + } = {}): Observable { + return this.request('DELETE', url, options as any); + } + + + /** + * Construct a GET request which interprets the body as an `ArrayBuffer` and returns it. + * + * @return an `Observable` of the body as an `ArrayBuffer`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable; + + /** + * Construct a GET request which interprets the body as a `Blob` and returns it. + * + * @return an `Observable` of the body as a `Blob`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable; + + /** + * Construct a GET request which interprets the body as text and returns it. + * + * @return an `Observable` of the body as a `string`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable; + + /** + * Construct a GET request which interprets the body as an `ArrayBuffer` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a GET request which interprets the body as a `Blob` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Blob`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a GET request which interprets the body as text and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `string`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a GET request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Object`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a GET request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `T`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a GET request which interprets the body as an `ArrayBuffer` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `ArrayBuffer`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a GET request which interprets the body as a `Blob` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Blob`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a GET request which interprets the body as text and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `string`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a GET request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Object`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a GET request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `T`. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a GET request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as an `Object`. + */ + get(url: string, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Construct a GET request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as type `T`. + */ + get(url: string, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Constructs an `Observable` which, when subscribed, will cause the configured + * GET request to be executed on the server. See the individual overloads for + * details of `get()`'s return type based on the provided options. + */ + get(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: HttpObserve, + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + } = {}): Observable { + return this.request('GET', url, options as any); + } + + + /** + * Construct a HEAD request which interprets the body as an `ArrayBuffer` and returns it. + * + * @return an `Observable` of the body as an `ArrayBuffer`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + + /** + * Construct a HEAD request which interprets the body as a `Blob` and returns it. + * + * @return an `Observable` of the body as a `Blob`. + */ + }): Observable; + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable; + + /** + * Construct a HEAD request which interprets the body as text and returns it. + * + * @return an `Observable` of the body as a `string`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable; + + /** + * Construct a HEAD request which interprets the body as an `ArrayBuffer` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a HEAD request which interprets the body as a `Blob` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Blob`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a HEAD request which interprets the body as text and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `string`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a HEAD request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Object`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a HEAD request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `T`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a HEAD request which interprets the body as an `ArrayBuffer` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `ArrayBuffer`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a HEAD request which interprets the body as a `Blob` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Blob`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a HEAD request which interprets the body as text and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `string`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a HEAD request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Object`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a HEAD request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `T`. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a HEAD request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as an `Object`. + */ + head(url: string, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Construct a HEAD request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as type `T`. + */ + head(url: string, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Constructs an `Observable` which, when subscribed, will cause the configured + * HEAD request to be executed on the server. See the individual overloads for + * details of `head()`'s return type based on the provided options. + */ + head(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: HttpObserve, + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + } = {}): Observable { + return this.request('HEAD', url, options as any); + } + + /** + * Construct a JSONP request for the given URL and name of the callback parameter. + * + * @return an `Observable` of the response object as an `Object` + */ + jsonp(url: string, callbackParam: string): Observable; + + /** + * Construct a JSONP request for the given URL and name of the callback parameter. + * + * @return an `Observable` of the response object as type `T`. + */ + jsonp(url: string, callbackParam: string): Observable; + + /** + * Constructs an `Observable` which, when subscribed, will cause a request + * with the special method `JSONP` to be dispatched via the interceptor pipeline. + * + * A suitable interceptor must be installed (e.g. via the `HttpClientJsonpModule`). + * If no such interceptor is reached, then the `JSONP` request will likely be + * rejected by the configured backend. + */ + jsonp(url: string, callbackParam: string): Observable { + return this.request('JSONP', url, { + params: new HttpParams().append(callbackParam, 'JSONP_CALLBACK'), + observe: 'body', + responseType: 'json', + }); + } + + /** + * Make an OPTIONS request which interprets the body as an `ArrayBuffer` and returns it. + * + * @return an `Observable` of the body as an `ArrayBuffer`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable; + + /** + * Construct an OPTIONS request which interprets the body as a `Blob` and returns it. + * + * @return an `Observable` of the body as a `Blob`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable; + + /** + * Construct an OPTIONS request which interprets the body as text and returns it. + * + * @return an `Observable` of the body as a `string`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable; + + /** + * Construct an OPTIONS request which interprets the body as an `ArrayBuffer` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct an OPTIONS request which interprets the body as a `Blob` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Blob`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct an OPTIONS request which interprets the body as text and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `string`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct an OPTIONS request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Object`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct an OPTIONS request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `T`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct an OPTIONS request which interprets the body as an `ArrayBuffer` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `ArrayBuffer`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct an OPTIONS request which interprets the body as a `Blob` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Blob`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct an OPTIONS request which interprets the body as text and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `string`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct an OPTIONS request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Object`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct an OPTIONS request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `T`. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct an OPTIONS request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as an `Object`. + */ + options(url: string, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Construct an OPTIONS request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as type `T`. + */ + options(url: string, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Constructs an `Observable` which, when subscribed, will cause the configured + * OPTIONS request to be executed on the server. See the individual overloads for + * details of `options()`'s return type based on the provided options. + */ + options(url: string, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: HttpObserve, + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + } = {}): Observable { + return this.request('OPTIONS', url, options as any); + } + + /** + * Construct a PATCH request which interprets the body as an `ArrayBuffer` and returns it. + * + * @return an `Observable` of the body as an `ArrayBuffer`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable; + + /** + * Construct a PATCH request which interprets the body as a `Blob` and returns it. + * + * @return an `Observable` of the body as a `Blob`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable; + + /** + * Construct a PATCH request which interprets the body as text and returns it. + * + * @return an `Observable` of the body as a `string`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable; + + /** + * Construct a PATCH request which interprets the body as an `ArrayBuffer` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PATCH request which interprets the body as a `Blob` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Blob`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PATCH request which interprets the body as text and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `string`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PATCH request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Object`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PATCH request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `T`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PATCH request which interprets the body as an `ArrayBuffer` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `ArrayBuffer`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PATCH request which interprets the body as a `Blob` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Blob`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PATCH request which interprets the body as text and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `string`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PATCH request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Object`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PATCH request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `T`. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PATCH request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as an `Object`. + */ + patch(url: string, body: any|null, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Construct a PATCH request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as type `T`. + */ + patch(url: string, body: any|null, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Constructs an `Observable` which, when subscribed, will cause the configured + * PATCH request to be executed on the server. See the individual overloads for + * details of `patch()`'s return type based on the provided options. + */ + patch(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: HttpObserve, + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + } = {}): Observable { + return this.request('PATCH', url, addBody(options, body)); + } + + /** + * Construct a POST request which interprets the body as an `ArrayBuffer` and returns it. + * + * @return an `Observable` of the body as an `ArrayBuffer`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable; + + /** + * Construct a POST request which interprets the body as a `Blob` and returns it. + * + * @return an `Observable` of the body as a `Blob`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable; + + /** + * Construct a POST request which interprets the body as text and returns it. + * + * @return an `Observable` of the body as a `string`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable; + + /** + * Construct a POST request which interprets the body as an `ArrayBuffer` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a POST request which interprets the body as a `Blob` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Blob`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a POST request which interprets the body as text and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `string`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a POST request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Object`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a POST request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `T`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a POST request which interprets the body as an `ArrayBuffer` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `ArrayBuffer`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a POST request which interprets the body as a `Blob` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Blob`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a POST request which interprets the body as text and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `string`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a POST request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Object`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a POST request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `T`. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a POST request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as an `Object`. + */ + post(url: string, body: any|null, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Construct a POST request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as type `T`. + */ + post(url: string, body: any|null, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Constructs an `Observable` which, when subscribed, will cause the configured + * POST request to be executed on the server. See the individual overloads for + * details of `post()`'s return type based on the provided options. + */ + post(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: HttpObserve, + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + } = {}): Observable { + return this.request('POST', url, addBody(options, body)); + } + + /** + * Construct a PUT request which interprets the body as an `ArrayBuffer` and returns it. + * + * @return an `Observable` of the body as an `ArrayBuffer`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable; + + /** + * Construct a PUT request which interprets the body as a `Blob` and returns it. + * + * @return an `Observable` of the body as a `Blob`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable; + + /** + * Construct a PUT request which interprets the body as text and returns it. + * + * @return an `Observable` of the body as a `string`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable; + + /** + * Construct a PUT request which interprets the body as an `ArrayBuffer` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PUT request which interprets the body as a `Blob` and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Blob`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PUT request which interprets the body as text and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `string`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PUT request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `Object`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PUT request which interprets the body as JSON and returns the full event stream. + * + * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `T`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'events', responseType?: 'json', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PUT request which interprets the body as an `ArrayBuffer` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `ArrayBuffer`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'arraybuffer', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PUT request which interprets the body as a `Blob` and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Blob`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'blob', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PUT request which interprets the body as text and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `string`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType: 'text', withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PUT request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `Object`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PUT request which interprets the body as JSON and returns the full response. + * + * @return an `Observable` of the `HttpResponse` for the request, with a body type of `T`. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe: 'response', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable>; + + /** + * Construct a PUT request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as an `Object`. + */ + put(url: string, body: any|null, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Construct a PUT request which interprets the body as JSON and returns it. + * + * @return an `Observable` of the body as type `T`. + */ + put(url: string, body: any|null, options?: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: 'body', + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'json', + withCredentials?: boolean, + }): Observable; + + /** + * Constructs an `Observable` which, when subscribed, will cause the configured + * PUT request to be executed on the server. See the individual overloads for + * details of `put()`'s return type based on the provided options. + */ + put(url: string, body: any|null, options: { + headers?: HttpHeaders | {[header: string]: string | string[]}, + observe?: HttpObserve, + params?: HttpParams|{[param: string]: string | string[]}, + reportProgress?: boolean, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + } = {}): Observable { + return this.request('PUT', url, addBody(options, body)); + } +} diff --git a/packages/http/src/headers.ts b/packages/http/src/headers.ts new file mode 100644 index 00000000..a4c39e4f --- /dev/null +++ b/packages/http/src/headers.ts @@ -0,0 +1,216 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +interface Update { + name: string; + value?: string|string[]; + op: 'a'|'s'|'d'; +} + +/** + * Immutable set of Http headers, with lazy parsing. + * + */ +export class HttpHeaders { + /** + * Internal map of lowercase header names to values. + */ + // TODO(issue/24571): remove '!'. + private headers !: Map; + + + /** + * Internal map of lowercased header names to the normalized + * form of the name (the form seen first). + */ + private normalizedNames: Map = new Map(); + + /** + * Complete the lazy initialization of this object (needed before reading). + */ + // TODO(issue/24571): remove '!'. + private lazyInit !: HttpHeaders | Function | null; + + /** + * Queued updates to be materialized the next initialization. + */ + private lazyUpdate: Update[]|null = null; + + constructor(headers?: string|{[name: string]: string | string[]}) { + if (!headers) { + this.headers = new Map(); + } else if (typeof headers === 'string') { + this.lazyInit = () => { + this.headers = new Map(); + headers.split('\n').forEach(line => { + const index = line.indexOf(':'); + if (index > 0) { + const name = line.slice(0, index); + const key = name.toLowerCase(); + const value = line.slice(index + 1).trim(); + this.maybeSetNormalizedName(name, key); + if (this.headers.has(key)) { + this.headers.get(key) !.push(value); + } else { + this.headers.set(key, [value]); + } + } + }); + }; + } else { + this.lazyInit = () => { + this.headers = new Map(); + Object.keys(headers).forEach(name => { + let values: string|string[] = headers[name]; + const key = name.toLowerCase(); + if (typeof values === 'string') { + values = [values]; + } + if (values.length > 0) { + this.headers.set(key, values); + this.maybeSetNormalizedName(name, key); + } + }); + }; + } + } + + /** + * Checks for existence of header by given name. + */ + has(name: string): boolean { + this.init(); + + return this.headers.has(name.toLowerCase()); + } + + /** + * Returns first header that matches given name. + */ + get(name: string): string|null { + this.init(); + + const values = this.headers.get(name.toLowerCase()); + return values && values.length > 0 ? values[0] : null; + } + + /** + * Returns the names of the headers + */ + keys(): string[] { + this.init(); + + return Array.from(this.normalizedNames.values()); + } + + /** + * Returns list of header values for a given name. + */ + getAll(name: string): string[]|null { + this.init(); + + return this.headers.get(name.toLowerCase()) || null; + } + + append(name: string, value: string|string[]): HttpHeaders { + return this.clone({name, value, op: 'a'}); + } + + set(name: string, value: string|string[]): HttpHeaders { + return this.clone({name, value, op: 's'}); + } + + delete (name: string, value?: string|string[]): HttpHeaders { + return this.clone({name, value, op: 'd'}); + } + + private maybeSetNormalizedName(name: string, lcName: string): void { + if (!this.normalizedNames.has(lcName)) { + this.normalizedNames.set(lcName, name); + } + } + + private init(): void { + if (!!this.lazyInit) { + if (this.lazyInit instanceof HttpHeaders) { + this.copyFrom(this.lazyInit); + } else { + this.lazyInit(); + } + this.lazyInit = null; + if (!!this.lazyUpdate) { + this.lazyUpdate.forEach(update => this.applyUpdate(update)); + this.lazyUpdate = null; + } + } + } + + private copyFrom(other: HttpHeaders) { + other.init(); + Array.from(other.headers.keys()).forEach(key => { + this.headers.set(key, other.headers.get(key) !); + this.normalizedNames.set(key, other.normalizedNames.get(key) !); + }); + } + + private clone(update: Update): HttpHeaders { + const clone = new HttpHeaders(); + clone.lazyInit = + (!!this.lazyInit && this.lazyInit instanceof HttpHeaders) ? this.lazyInit : this; + clone.lazyUpdate = (this.lazyUpdate || []).concat([update]); + return clone; + } + + private applyUpdate(update: Update): void { + const key = update.name.toLowerCase(); + switch (update.op) { + case 'a': + case 's': + let value = update.value !; + if (typeof value === 'string') { + value = [value]; + } + if (value.length === 0) { + return; + } + this.maybeSetNormalizedName(update.name, key); + const base = (update.op === 'a' ? this.headers.get(key) : undefined) || []; + base.push(...value); + this.headers.set(key, base); + break; + case 'd': + const toDelete = update.value as string | undefined; + if (!toDelete) { + this.headers.delete(key); + this.normalizedNames.delete(key); + } else { + let existing = this.headers.get(key); + if (!existing) { + return; + } + existing = existing.filter(value => toDelete.indexOf(value) === -1); + if (existing.length === 0) { + this.headers.delete(key); + this.normalizedNames.delete(key); + } else { + this.headers.set(key, existing); + } + } + break; + } + } + + /** + * @internal + */ + forEach(fn: (name: string, values: string[]) => void) { + this.init(); + Array.from(this.normalizedNames.keys()) + .forEach(key => fn(this.normalizedNames.get(key) !, this.headers.get(key) !)); + } +} diff --git a/packages/http/src/http.ts b/packages/http/src/http.ts deleted file mode 100644 index 87d59f33..00000000 --- a/packages/http/src/http.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { HttpError } from "@alterior/common"; -import { Injectable } from "@alterior/di"; - -@Injectable() -export class Http { - constructor() { - - } - - async get(url : string): Promise { - return await this.fetchData(url, { - method: 'GET' - }); - } - - async put(url : string, object : T): Promise { - return await this.fetchData(url, { - method: 'PUT', - body: JSON.stringify(object) - }); - } - - async post(url : string, object : T): Promise { - return await this.fetchData(url, { - method: 'POST', - body: JSON.stringify(object) - }); - } - - async patch(url : string, object : Partial): Promise { - return await this.fetchData(url, { - method: 'PATCH', - body: JSON.stringify(object) - }); - } - - async delete(url : string): Promise { - return await this.fetchData(url, { - method: 'DELETE' - }); - } - - async fetchData(url : string, init? : RequestInit): Promise { - let response = await this.fetchResponse(url); - let headersMap : string[][] = []; - response.headers.forEach((value, key) => headersMap.push([key, value])); - - if (response.status >= 400) - throw new HttpError(response.status, headersMap, await response.json()); - - return await response.json(); - } - - async fetchResponse(url : string, init? : RequestInit): Promise { - return await fetch(url, init); - } -} \ No newline at end of file diff --git a/packages/http/src/index.ts b/packages/http/src/index.ts index f1cf0a58..5ef5c28f 100644 --- a/packages/http/src/index.ts +++ b/packages/http/src/index.ts @@ -1 +1,18 @@ -export * from './http'; \ No newline at end of file +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export {HttpBackend, HttpHandler} from './backend'; +export {HttpClient} from './client'; +export {HttpHeaders} from './headers'; +export {HTTP_INTERCEPTORS, HttpInterceptor} from './interceptor'; +export {HttpClientModule, HttpClientXsrfModule, HttpInterceptingHandler as ɵHttpInterceptingHandler} from './module'; +export {HttpParameterCodec, HttpParams, HttpUrlEncodingCodec} from './params'; +export {HttpRequest} from './request'; +export {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpResponseBase, HttpSentEvent, HttpUserEvent} from './response'; +export {HttpXhrBackend, XhrFactory} from './xhr'; +export {HttpXsrfTokenExtractor} from './xsrf'; diff --git a/packages/http/src/interceptor.ts b/packages/http/src/interceptor.ts new file mode 100644 index 00000000..5e660562 --- /dev/null +++ b/packages/http/src/interceptor.ts @@ -0,0 +1,73 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Injectable, InjectionToken} from '@alterior/di'; +import {Observable} from 'rxjs'; + +import {HttpHandler} from './backend'; +import {HttpRequest} from './request'; +import {HttpEvent} from './response'; + +/** + * Intercepts `HttpRequest` and handles them. + * + * Most interceptors will transform the outgoing request before passing it to the + * next interceptor in the chain, by calling `next.handle(transformedReq)`. + * + * In rare cases, interceptors may wish to completely handle a request themselves, + * and not delegate to the remainder of the chain. This behavior is allowed. + * + * + */ +export interface HttpInterceptor { + /** + * Intercept an outgoing `HttpRequest` and optionally transform it or the + * response. + * + * Typically an interceptor will transform the outgoing request before returning + * `next.handle(transformedReq)`. An interceptor may choose to transform the + * response event stream as well, by applying additional Rx operators on the stream + * returned by `next.handle()`. + * + * More rarely, an interceptor may choose to completely handle the request itself, + * and compose a new event stream instead of invoking `next.handle()`. This is + * acceptable behavior, but keep in mind further interceptors will be skipped entirely. + * + * It is also rare but valid for an interceptor to return multiple responses on the + * event stream for a single request. + */ + intercept(req: HttpRequest, next: HttpHandler): Observable>; +} + +/** + * `HttpHandler` which applies an `HttpInterceptor` to an `HttpRequest`. + * + * + */ +export class HttpInterceptorHandler implements HttpHandler { + constructor(private next: HttpHandler, private interceptor: HttpInterceptor) {} + + handle(req: HttpRequest): Observable> { + return this.interceptor.intercept(req, this.next); + } +} + +/** + * A multi-provider token which represents the array of `HttpInterceptor`s that + * are registered. + * + * + */ +export const HTTP_INTERCEPTORS = new InjectionToken('HTTP_INTERCEPTORS'); + +@Injectable() +export class NoopInterceptor implements HttpInterceptor { + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return next.handle(req); + } +} diff --git a/packages/http/src/module.ts b/packages/http/src/module.ts new file mode 100644 index 00000000..c5060372 --- /dev/null +++ b/packages/http/src/module.ts @@ -0,0 +1,164 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Injectable, Module, ConfiguredModule, Injector} from '@alterior/di'; +import {Observable} from 'rxjs'; + +import {HttpBackend, HttpHandler} from './backend'; +import {HttpClient} from './client'; +import {HTTP_INTERCEPTORS, HttpInterceptor, HttpInterceptorHandler, NoopInterceptor} from './interceptor'; +import {HttpRequest} from './request'; +import {HttpEvent} from './response'; +import {BrowserXhr, HttpXhrBackend, XhrFactory} from './xhr'; +import {HttpXsrfInterceptor, HttpXsrfTokenExtractor, XSRF_COOKIE_NAME, XSRF_HEADER_NAME} from './xsrf'; + +/** + * An injectable `HttpHandler` that applies multiple interceptors + * to a request before passing it to the given `HttpBackend`. + * + * The interceptors are loaded lazily from the injector, to allow + * interceptors to themselves inject classes depending indirectly + * on `HttpInterceptingHandler` itself. + * @see `HttpInterceptor` + */ +@Injectable() +export class HttpInterceptingHandler implements HttpHandler { + private chain: HttpHandler|null = null; + + constructor(private backend: HttpBackend, private injector: Injector) {} + + handle(req: HttpRequest): Observable> { + if (this.chain === null) { + const interceptors = this.injector.get(HTTP_INTERCEPTORS, []); + this.chain = interceptors.reduceRight( + (next, interceptor) => new HttpInterceptorHandler(next, interceptor), this.backend); + } + + return this.chain.handle(req); + } +} + +/** + * Constructs an `HttpHandler` that applies interceptors + * to a request before passing it to the given `HttpBackend`. + * + * Use as a factory function within `HttpClientModule`. + * + * + */ +export function interceptingHandler( + backend: HttpBackend, interceptors: HttpInterceptor[] | null = []): HttpHandler { + if (!interceptors) { + return backend; + } + return interceptors.reduceRight( + (next, interceptor) => new HttpInterceptorHandler(next, interceptor), backend); +} + +/** + * Factory function that determines where to store JSONP callbacks. + * + * Ordinarily JSONP callbacks are stored on the `window` object, but this may not exist + * in test environments. In that case, callbacks are stored on an anonymous object instead. + * + * + */ +export function jsonpCallbackContext(): Object { + if (typeof window === 'object') { + return window; + } + return {}; +} + +/** + * An NgModule that adds XSRF protection support to outgoing requests. + * + * For a server that supports a cookie-based XSRF protection system, + * use directly to configure XSRF protection with the correct + * cookie and header names. + * + * If no names are supplied, the default cookie name is `XSRF-TOKEN` + * and the default header name is `X-XSRF-TOKEN`. + * + * + */ +@Module({ + providers: [ + HttpXsrfInterceptor, + {provide: HTTP_INTERCEPTORS, useExisting: HttpXsrfInterceptor, multi: true}, + {provide: XSRF_COOKIE_NAME, useValue: 'XSRF-TOKEN'}, + {provide: XSRF_HEADER_NAME, useValue: 'X-XSRF-TOKEN'}, + ], +}) +export class HttpClientXsrfModule { + /** + * Disable the default XSRF protection. + */ + static disable(): ConfiguredModule { + return { + $module: HttpClientXsrfModule, + providers: [ + {provide: HttpXsrfInterceptor, useClass: NoopInterceptor}, + ], + }; + } + + /** + * Configure XSRF protection. + * @param options An object that can specify either or both + * cookie name or header name. + * - Cookie name default is `XSRF-TOKEN`. + * - Header name default is `X-XSRF-TOKEN`. + * + */ + static withOptions(options: { + cookieName?: string, + headerName?: string, + } = {}): ConfiguredModule { + return { + $module: HttpClientXsrfModule, + providers: [ + options.cookieName ? {provide: XSRF_COOKIE_NAME, useValue: options.cookieName} : [], + options.headerName ? {provide: XSRF_HEADER_NAME, useValue: options.headerName} : [], + ], + }; + } +} + +/** + * An NgModule that provides the `HttpClient` and associated services. + * + * Interceptors can be added to the chain behind `HttpClient` by binding them + * to the multiprovider for `HTTP_INTERCEPTORS`. + * + * + */ +@Module({ + /** + * Optional configuration for XSRF protection. + */ + imports: [ + // HttpClientXsrfModule.withOptions({ + // cookieName: 'XSRF-TOKEN', + // headerName: 'X-XSRF-TOKEN', + // }), + ], + /** + * The module provides `HttpClient` itself, and supporting services. + */ + providers: [ + HttpClient, + {provide: HttpHandler, useClass: HttpInterceptingHandler}, + HttpXhrBackend, + {provide: HttpBackend, useExisting: HttpXhrBackend}, + BrowserXhr, + {provide: XhrFactory, useExisting: BrowserXhr}, + ], +}) +export class HttpClientModule { +} diff --git a/packages/http/src/params.ts b/packages/http/src/params.ts new file mode 100644 index 00000000..09c9cf42 --- /dev/null +++ b/packages/http/src/params.ts @@ -0,0 +1,231 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** + * A codec for encoding and decoding parameters in URLs. + * + * Used by `HttpParams`. + * + * + **/ +export interface HttpParameterCodec { + encodeKey(key: string): string; + encodeValue(value: string): string; + + decodeKey(key: string): string; + decodeValue(value: string): string; +} + +/** + * A `HttpParameterCodec` that uses `encodeURIComponent` and `decodeURIComponent` to + * serialize and parse URL parameter keys and values. + * + * + */ +export class HttpUrlEncodingCodec implements HttpParameterCodec { + encodeKey(key: string): string { return standardEncoding(key); } + + encodeValue(value: string): string { return standardEncoding(value); } + + decodeKey(key: string): string { return decodeURIComponent(key); } + + decodeValue(value: string) { return decodeURIComponent(value); } +} + + +function paramParser(rawParams: string, codec: HttpParameterCodec): Map { + const map = new Map(); + if (rawParams.length > 0) { + const params: string[] = rawParams.split('&'); + params.forEach((param: string) => { + const eqIdx = param.indexOf('='); + const [key, val]: string[] = eqIdx == -1 ? + [codec.decodeKey(param), ''] : + [codec.decodeKey(param.slice(0, eqIdx)), codec.decodeValue(param.slice(eqIdx + 1))]; + const list = map.get(key) || []; + list.push(val); + map.set(key, list); + }); + } + return map; +} +function standardEncoding(v: string): string { + return encodeURIComponent(v) + .replace(/%40/gi, '@') + .replace(/%3A/gi, ':') + .replace(/%24/gi, '$') + .replace(/%2C/gi, ',') + .replace(/%3B/gi, ';') + .replace(/%2B/gi, '+') + .replace(/%3D/gi, '=') + .replace(/%3F/gi, '?') + .replace(/%2F/gi, '/'); +} + +interface Update { + param: string; + value?: string; + op: 'a'|'d'|'s'; +} + +/** Options used to construct an `HttpParams` instance. */ +export interface HttpParamsOptions { + /** + * String representation of the HTTP params in URL-query-string format. Mutually exclusive with + * `fromObject`. + */ + fromString?: string; + + /** Object map of the HTTP params. Mutally exclusive with `fromString`. */ + fromObject?: {[param: string]: string | string[]}; + + /** Encoding codec used to parse and serialize the params. */ + encoder?: HttpParameterCodec; +} + +/** + * An HTTP request/response body that represents serialized parameters, + * per the MIME type `application/x-www-form-urlencoded`. + * + * This class is immutable - all mutation operations return a new instance. + * + * + */ +export class HttpParams { + private map: Map|null; + private encoder: HttpParameterCodec; + private updates: Update[]|null = null; + private cloneFrom: HttpParams|null = null; + + constructor(options: HttpParamsOptions = {} as HttpParamsOptions) { + this.encoder = options.encoder || new HttpUrlEncodingCodec(); + if (!!options.fromString) { + if (!!options.fromObject) { + throw new Error(`Cannot specify both fromString and fromObject.`); + } + this.map = paramParser(options.fromString, this.encoder); + } else if (!!options.fromObject) { + this.map = new Map(); + Object.keys(options.fromObject).forEach(key => { + const value = (options.fromObject as any)[key]; + this.map !.set(key, Array.isArray(value) ? value : [value]); + }); + } else { + this.map = null; + } + } + + /** + * Check whether the body has one or more values for the given parameter name. + */ + has(param: string): boolean { + this.init(); + return this.map !.has(param); + } + + /** + * Get the first value for the given parameter name, or `null` if it's not present. + */ + get(param: string): string|null { + this.init(); + const res = this.map !.get(param); + return !!res ? res[0] : null; + } + + /** + * Get all values for the given parameter name, or `null` if it's not present. + */ + getAll(param: string): string[]|null { + this.init(); + return this.map !.get(param) || null; + } + + /** + * Get all the parameter names for this body. + */ + keys(): string[] { + this.init(); + return Array.from(this.map !.keys()); + } + + /** + * Construct a new body with an appended value for the given parameter name. + */ + append(param: string, value: string): HttpParams { return this.clone({param, value, op: 'a'}); } + + /** + * Construct a new body with a new value for the given parameter name. + */ + set(param: string, value: string): HttpParams { return this.clone({param, value, op: 's'}); } + + /** + * Construct a new body with either the given value for the given parameter + * removed, if a value is given, or all values for the given parameter removed + * if not. + */ + delete (param: string, value?: string): HttpParams { return this.clone({param, value, op: 'd'}); } + + /** + * Serialize the body to an encoded string, where key-value pairs (separated by `=`) are + * separated by `&`s. + */ + toString(): string { + this.init(); + return this.keys() + .map(key => { + const eKey = this.encoder.encodeKey(key); + return this.map !.get(key) !.map(value => eKey + '=' + this.encoder.encodeValue(value)) + .join('&'); + }) + .join('&'); + } + + private clone(update: Update): HttpParams { + const clone = new HttpParams({ encoder: this.encoder } as HttpParamsOptions); + clone.cloneFrom = this.cloneFrom || this; + clone.updates = (this.updates || []).concat([update]); + return clone; + } + + private init() { + if (this.map === null) { + this.map = new Map(); + } + if (this.cloneFrom !== null) { + this.cloneFrom.init(); + this.cloneFrom.keys().forEach(key => this.map !.set(key, this.cloneFrom !.map !.get(key) !)); + this.updates !.forEach(update => { + switch (update.op) { + case 'a': + case 's': + const base = (update.op === 'a' ? this.map !.get(update.param) : undefined) || []; + base.push(update.value !); + this.map !.set(update.param, base); + break; + case 'd': + if (update.value !== undefined) { + let base = this.map !.get(update.param) || []; + const idx = base.indexOf(update.value); + if (idx !== -1) { + base.splice(idx, 1); + } + if (base.length > 0) { + this.map !.set(update.param, base); + } else { + this.map !.delete(update.param); + } + } else { + this.map !.delete(update.param); + break; + } + } + }); + this.cloneFrom = null; + } + } +} diff --git a/packages/http/src/request.ts b/packages/http/src/request.ts new file mode 100644 index 00000000..30413cb8 --- /dev/null +++ b/packages/http/src/request.ts @@ -0,0 +1,387 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {HttpHeaders} from './headers'; +import {HttpParams} from './params'; + +/** + * Construction interface for `HttpRequest`s. + * + * All values are optional and will override default values if provided. + */ +interface HttpRequestInit { + headers?: HttpHeaders; + reportProgress?: boolean; + params?: HttpParams; + responseType?: 'arraybuffer'|'blob'|'json'|'text'; + withCredentials?: boolean; +} + +/** + * Determine whether the given HTTP method may include a body. + */ +function mightHaveBody(method: string): boolean { + switch (method) { + case 'DELETE': + case 'GET': + case 'HEAD': + case 'OPTIONS': + case 'JSONP': + return false; + default: + return true; + } +} + +/** + * Safely assert whether the given value is an ArrayBuffer. + * + * In some execution environments ArrayBuffer is not defined. + */ +function isArrayBuffer(value: any): value is ArrayBuffer { + return typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer; +} + +/** + * Safely assert whether the given value is a Blob. + * + * In some execution environments Blob is not defined. + */ +function isBlob(value: any): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +/** + * Safely assert whether the given value is a FormData instance. + * + * In some execution environments FormData is not defined. + */ +function isFormData(value: any): value is FormData { + return typeof FormData !== 'undefined' && value instanceof FormData; +} + +/** + * An outgoing HTTP request with an optional typed body. + * + * `HttpRequest` represents an outgoing request, including URL, method, + * headers, body, and other request configuration options. Instances should be + * assumed to be immutable. To modify a `HttpRequest`, the `clone` + * method should be used. + * + * + */ +export class HttpRequest { + /** + * The request body, or `null` if one isn't set. + * + * Bodies are not enforced to be immutable, as they can include a reference to any + * user-defined data type. However, interceptors should take care to preserve + * idempotence by treating them as such. + */ + readonly body: T|null = null; + + /** + * Outgoing headers for this request. + */ + // TODO(issue/24571): remove '!'. + readonly headers !: HttpHeaders; + + /** + * Whether this request should be made in a way that exposes progress events. + * + * Progress events are expensive (change detection runs on each event) and so + * they should only be requested if the consumer intends to monitor them. + */ + readonly reportProgress: boolean = false; + + /** + * Whether this request should be sent with outgoing credentials (cookies). + */ + readonly withCredentials: boolean = false; + + /** + * The expected response type of the server. + * + * This is used to parse the response appropriately before returning it to + * the requestee. + */ + readonly responseType: 'arraybuffer'|'blob'|'json'|'text' = 'json'; + + /** + * The outgoing HTTP request method. + */ + readonly method: string; + + /** + * Outgoing URL parameters. + */ + // TODO(issue/24571): remove '!'. + readonly params !: HttpParams; + + /** + * The outgoing URL with all URL parameters set. + */ + readonly urlWithParams: string; + + constructor(method: 'DELETE'|'GET'|'HEAD'|'JSONP'|'OPTIONS', url: string, init?: { + headers?: HttpHeaders, + reportProgress?: boolean, + params?: HttpParams, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + }); + constructor(method: 'POST'|'PUT'|'PATCH', url: string, body: T|null, init?: { + headers?: HttpHeaders, + reportProgress?: boolean, + params?: HttpParams, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + }); + constructor(method: string, url: string, body: T|null, init?: { + headers?: HttpHeaders, + reportProgress?: boolean, + params?: HttpParams, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + }); + constructor( + method: string, readonly url: string, third?: T|{ + headers?: HttpHeaders, + reportProgress?: boolean, + params?: HttpParams, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + }|null, + fourth?: { + headers?: HttpHeaders, + reportProgress?: boolean, + params?: HttpParams, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + }) { + this.method = method.toUpperCase(); + // Next, need to figure out which argument holds the HttpRequestInit + // options, if any. + let options: HttpRequestInit|undefined; + + // Check whether a body argument is expected. The only valid way to omit + // the body argument is to use a known no-body method like GET. + if (mightHaveBody(this.method) || !!fourth) { + // Body is the third argument, options are the fourth. + this.body = (third !== undefined) ? third as T : null; + options = fourth; + } else { + // No body required, options are the third argument. The body stays null. + options = third as HttpRequestInit; + } + + // If options have been passed, interpret them. + if (options) { + // Normalize reportProgress and withCredentials. + this.reportProgress = !!options.reportProgress; + this.withCredentials = !!options.withCredentials; + + // Override default response type of 'json' if one is provided. + if (!!options.responseType) { + this.responseType = options.responseType; + } + + // Override headers if they're provided. + if (!!options.headers) { + this.headers = options.headers; + } + + if (!!options.params) { + this.params = options.params; + } + } + + // If no headers have been passed in, construct a new HttpHeaders instance. + if (!this.headers) { + this.headers = new HttpHeaders(); + } + + // If no parameters have been passed in, construct a new HttpUrlEncodedParams instance. + if (!this.params) { + this.params = new HttpParams(); + this.urlWithParams = url; + } else { + // Encode the parameters to a string in preparation for inclusion in the URL. + const params = this.params.toString(); + if (params.length === 0) { + // No parameters, the visible URL is just the URL given at creation time. + this.urlWithParams = url; + } else { + // Does the URL already have query parameters? Look for '?'. + const qIdx = url.indexOf('?'); + // There are 3 cases to handle: + // 1) No existing parameters -> append '?' followed by params. + // 2) '?' exists and is followed by existing query string -> + // append '&' followed by params. + // 3) '?' exists at the end of the url -> append params directly. + // This basically amounts to determining the character, if any, with + // which to join the URL and parameters. + const sep: string = qIdx === -1 ? '?' : (qIdx < url.length - 1 ? '&' : ''); + this.urlWithParams = url + sep + params; + } + } + } + + /** + * Transform the free-form body into a serialized format suitable for + * transmission to the server. + */ + serializeBody(): ArrayBuffer|Blob|FormData|string|null { + // If no body is present, no need to serialize it. + if (this.body === null) { + return null; + } + // Check whether the body is already in a serialized form. If so, + // it can just be returned directly. + if (isArrayBuffer(this.body) || isBlob(this.body) || isFormData(this.body) || + typeof this.body === 'string') { + return this.body; + } + // Check whether the body is an instance of HttpUrlEncodedParams. + if (this.body instanceof HttpParams) { + return this.body.toString(); + } + // Check whether the body is an object or array, and serialize with JSON if so. + if (typeof this.body === 'object' || typeof this.body === 'boolean' || + Array.isArray(this.body)) { + return JSON.stringify(this.body); + } + // Fall back on toString() for everything else. + return (this.body as any).toString(); + } + + /** + * Examine the body and attempt to infer an appropriate MIME type + * for it. + * + * If no such type can be inferred, this method will return `null`. + */ + detectContentTypeHeader(): string|null { + // An empty body has no content type. + if (this.body === null) { + return null; + } + // FormData bodies rely on the browser's content type assignment. + if (isFormData(this.body)) { + return null; + } + // Blobs usually have their own content type. If it doesn't, then + // no type can be inferred. + if (isBlob(this.body)) { + return this.body.type || null; + } + // Array buffers have unknown contents and thus no type can be inferred. + if (isArrayBuffer(this.body)) { + return null; + } + // Technically, strings could be a form of JSON data, but it's safe enough + // to assume they're plain strings. + if (typeof this.body === 'string') { + return 'text/plain'; + } + // `HttpUrlEncodedParams` has its own content-type. + if (this.body instanceof HttpParams) { + return 'application/x-www-form-urlencoded;charset=UTF-8'; + } + // Arrays, objects, and numbers will be encoded as JSON. + if (typeof this.body === 'object' || typeof this.body === 'number' || + Array.isArray(this.body)) { + return 'application/json'; + } + // No type could be inferred. + return null; + } + + clone(): HttpRequest; + clone(update: { + headers?: HttpHeaders, + reportProgress?: boolean, + params?: HttpParams, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + body?: T|null, + method?: string, + url?: string, + setHeaders?: {[name: string]: string | string[]}, + setParams?: {[param: string]: string}, + }): HttpRequest; + clone(update: { + headers?: HttpHeaders, + reportProgress?: boolean, + params?: HttpParams, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + body?: V|null, + method?: string, + url?: string, + setHeaders?: {[name: string]: string | string[]}, + setParams?: {[param: string]: string}, + }): HttpRequest; + clone(update: { + headers?: HttpHeaders, + reportProgress?: boolean, + params?: HttpParams, + responseType?: 'arraybuffer'|'blob'|'json'|'text', + withCredentials?: boolean, + body?: any|null, + method?: string, + url?: string, + setHeaders?: {[name: string]: string | string[]}, + setParams?: {[param: string]: string}; + } = {}): HttpRequest { + // For method, url, and responseType, take the current value unless + // it is overridden in the update hash. + const method = update.method || this.method; + const url = update.url || this.url; + const responseType = update.responseType || this.responseType; + + // The body is somewhat special - a `null` value in update.body means + // whatever current body is present is being overridden with an empty + // body, whereas an `undefined` value in update.body implies no + // override. + const body = (update.body !== undefined) ? update.body : this.body; + + // Carefully handle the boolean options to differentiate between + // `false` and `undefined` in the update args. + const withCredentials = + (update.withCredentials !== undefined) ? update.withCredentials : this.withCredentials; + const reportProgress = + (update.reportProgress !== undefined) ? update.reportProgress : this.reportProgress; + + // Headers and params may be appended to if `setHeaders` or + // `setParams` are used. + let headers = update.headers || this.headers; + let params = update.params || this.params; + + // Check whether the caller has asked to add headers. + if (update.setHeaders !== undefined) { + // Set every requested header. + headers = + Object.keys(update.setHeaders) + .reduce((headers, name) => headers.set(name, update.setHeaders ![name]), headers); + } + + // Check whether the caller has asked to set params. + if (update.setParams) { + // Set every requested param. + params = Object.keys(update.setParams) + .reduce((params, param) => params.set(param, update.setParams ![param]), params); + } + + // Finally, construct the new HttpRequest using the pieces from above. + return new HttpRequest( + method, url, body, { + params, headers, reportProgress, responseType, withCredentials, + }); + } +} diff --git a/packages/http/src/response.ts b/packages/http/src/response.ts new file mode 100644 index 00000000..9a724990 --- /dev/null +++ b/packages/http/src/response.ts @@ -0,0 +1,330 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {HttpHeaders} from './headers'; + +/** + * Type enumeration for the different kinds of `HttpEvent`. + * + * + */ +export enum HttpEventType { + /** + * The request was sent out over the wire. + */ + Sent, + + /** + * An upload progress event was received. + */ + UploadProgress, + + /** + * The response status code and headers were received. + */ + ResponseHeader, + + /** + * A download progress event was received. + */ + DownloadProgress, + + /** + * The full response including the body was received. + */ + Response, + + /** + * A custom event from an interceptor or a backend. + */ + User, +} + +/** + * Base interface for progress events. + * + * + */ +export interface HttpProgressEvent { + /** + * Progress event type is either upload or download. + */ + type: HttpEventType.DownloadProgress|HttpEventType.UploadProgress; + + /** + * Number of bytes uploaded or downloaded. + */ + loaded: number; + + /** + * Total number of bytes to upload or download. Depending on the request or + * response, this may not be computable and thus may not be present. + */ + total?: number; +} + +/** + * A download progress event. + * + * + */ +export interface HttpDownloadProgressEvent extends HttpProgressEvent { + type: HttpEventType.DownloadProgress; + + /** + * The partial response body as downloaded so far. + * + * Only present if the responseType was `text`. + */ + partialText?: string; +} + +/** + * An upload progress event. + * + * + */ +export interface HttpUploadProgressEvent extends HttpProgressEvent { + type: HttpEventType.UploadProgress; +} + +/** + * An event indicating that the request was sent to the server. Useful + * when a request may be retried multiple times, to distinguish between + * retries on the final event stream. + * + * + */ +export interface HttpSentEvent { type: HttpEventType.Sent; } + +/** + * A user-defined event. + * + * Grouping all custom events under this type ensures they will be handled + * and forwarded by all implementations of interceptors. + * + * + */ +export interface HttpUserEvent { type: HttpEventType.User; } + +/** + * An error that represents a failed attempt to JSON.parse text coming back + * from the server. + * + * It bundles the Error object with the actual response body that failed to parse. + * + * + */ +export interface HttpJsonParseError { + error: Error; + text: string; +} + +/** + * Union type for all possible events on the response stream. + * + * Typed according to the expected type of the response. + * + * + */ +export type HttpEvent = + HttpSentEvent | HttpHeaderResponse | HttpResponse| HttpProgressEvent | HttpUserEvent; + +/** + * Base class for both `HttpResponse` and `HttpHeaderResponse`. + * + * + */ +export abstract class HttpResponseBase { + /** + * All response headers. + */ + readonly headers: HttpHeaders; + + /** + * Response status code. + */ + readonly status: number; + + /** + * Textual description of response status code. + * + * Do not depend on this. + */ + readonly statusText: string; + + /** + * URL of the resource retrieved, or null if not available. + */ + readonly url: string|null; + + /** + * Whether the status code falls in the 2xx range. + */ + readonly ok: boolean; + + /** + * Type of the response, narrowed to either the full response or the header. + */ + // TODO(issue/24571): remove '!'. + readonly type !: HttpEventType.Response | HttpEventType.ResponseHeader; + + /** + * Super-constructor for all responses. + * + * The single parameter accepted is an initialization hash. Any properties + * of the response passed there will override the default values. + */ + constructor( + init: { + headers?: HttpHeaders, + status?: number, + statusText?: string, + url?: string, + }, + defaultStatus: number = 200, defaultStatusText: string = 'OK') { + // If the hash has values passed, use them to initialize the response. + // Otherwise use the default values. + this.headers = init.headers || new HttpHeaders(); + this.status = init.status !== undefined ? init.status : defaultStatus; + this.statusText = init.statusText || defaultStatusText; + this.url = init.url || null; + + // Cache the ok value to avoid defining a getter. + this.ok = this.status >= 200 && this.status < 300; + } +} + +/** + * A partial HTTP response which only includes the status and header data, + * but no response body. + * + * `HttpHeaderResponse` is a `HttpEvent` available on the response + * event stream, only when progress events are requested. + * + * + */ +export class HttpHeaderResponse extends HttpResponseBase { + /** + * Create a new `HttpHeaderResponse` with the given parameters. + */ + constructor(init: { + headers?: HttpHeaders, + status?: number, + statusText?: string, + url?: string, + } = {}) { + super(init); + } + + readonly type: HttpEventType.ResponseHeader = HttpEventType.ResponseHeader; + + /** + * Copy this `HttpHeaderResponse`, overriding its contents with the + * given parameter hash. + */ + clone(update: {headers?: HttpHeaders; status?: number; statusText?: string; url?: string;} = {}): + HttpHeaderResponse { + // Perform a straightforward initialization of the new HttpHeaderResponse, + // overriding the current parameters with new ones if given. + return new HttpHeaderResponse({ + headers: update.headers || this.headers, + status: update.status !== undefined ? update.status : this.status, + statusText: update.statusText || this.statusText, + url: update.url || this.url || undefined, + }); + } +} + +/** + * A full HTTP response, including a typed response body (which may be `null` + * if one was not returned). + * + * `HttpResponse` is a `HttpEvent` available on the response event + * stream. + * + * + */ +export class HttpResponse extends HttpResponseBase { + /** + * The response body, or `null` if one was not returned. + */ + readonly body: T|null; + + /** + * Construct a new `HttpResponse`. + */ + constructor(init: { + body?: T | null, headers?: HttpHeaders; status?: number; statusText?: string; url?: string; + } = {}) { + super(init); + this.body = init.body !== undefined ? init.body : null; + } + + readonly type: HttpEventType.Response = HttpEventType.Response; + + clone(): HttpResponse; + clone(update: {headers?: HttpHeaders; status?: number; statusText?: string; url?: string;}): + HttpResponse; + clone(update: { + body?: V | null, headers?: HttpHeaders; status?: number; statusText?: string; url?: string; + }): HttpResponse; + clone(update: { + body?: any | null; headers?: HttpHeaders; status?: number; statusText?: string; url?: string; + } = {}): HttpResponse { + return new HttpResponse({ + body: (update.body !== undefined) ? update.body : this.body, + headers: update.headers || this.headers, + status: (update.status !== undefined) ? update.status : this.status, + statusText: update.statusText || this.statusText, + url: update.url || this.url || undefined, + }); + } +} + +/** + * A response that represents an error or failure, either from a + * non-successful HTTP status, an error while executing the request, + * or some other failure which occurred during the parsing of the response. + * + * Any error returned on the `Observable` response stream will be + * wrapped in an `HttpErrorResponse` to provide additional context about + * the state of the HTTP layer when the error occurred. The error property + * will contain either a wrapped Error object or the error response returned + * from the server. + * + * + */ +export class HttpErrorResponse extends HttpResponseBase implements Error { + readonly name = 'HttpErrorResponse'; + readonly message: string; + readonly error: any|null; + + /** + * Errors are never okay, even when the status code is in the 2xx success range. + */ + readonly ok = false; + + constructor(init: { + error?: any; headers?: HttpHeaders; status?: number; statusText?: string; url?: string; + }) { + // Initialize with a default status of 0 / Unknown Error. + super(init, 0, 'Unknown Error'); + + // If the response was successful, then this was a parse error. Otherwise, it was + // a protocol-level failure of some sort. Either the request failed in transit + // or the server returned an unsuccessful status code. + if (this.status >= 200 && this.status < 300) { + this.message = `Http failure during parsing for ${init.url || '(unknown url)'}`; + } else { + this.message = + `Http failure response for ${init.url || '(unknown url)'}: ${init.status} ${init.statusText}`; + } + this.error = init.error || null; + } +} diff --git a/packages/http/src/test/client.test.ts b/packages/http/src/test/client.test.ts new file mode 100644 index 00000000..5951f003 --- /dev/null +++ b/packages/http/src/test/client.test.ts @@ -0,0 +1,164 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {beforeEach, afterEach, describe, it} from 'razmin'; +import {toArray} from 'rxjs/operators'; +import {expect} from 'chai'; +import {HttpClient} from '../client'; +import {HttpErrorResponse, HttpEventType, HttpResponse} from '../response'; +import {HttpClientTestingBackend} from '../testing/backend'; + +describe('HttpClient', () => { + let client: HttpClient = null !; + let backend: HttpClientTestingBackend = null !; + beforeEach(() => { + backend = new HttpClientTestingBackend(); + client = new HttpClient(backend); + }); + afterEach(() => { backend.verify(); }); + describe('makes a basic request', () => { + it('for JSON data', done => { + client.get('/test').subscribe(res => { + expect((res as any)['data']).to.eq('hello world'); + done(); + }); + backend.expectOne('/test').flush({'data': 'hello world'}); + }); + it('for text data', done => { + client.get('/test', {responseType: 'text'}).subscribe(res => { + expect(res).to.eq('hello world'); + done(); + }); + backend.expectOne('/test').flush('hello world'); + }); + it('with headers', done => { + client.get('/test', {headers: {'X-Option': 'true'}}).subscribe(() => done()); + const req = backend.expectOne('/test'); + expect(req.request.headers.get('X-Option')).to.eq('true'); + req.flush({}); + }); + it('with params', done => { + client.get('/test', {params: {'test': 'true'}}).subscribe(() => done()); + backend.expectOne('/test?test=true').flush({}); + }); + it('for an arraybuffer', done => { + const body = new ArrayBuffer(4); + client.get('/test', {responseType: 'arraybuffer'}).subscribe(res => { + expect(res).to.eq(body); + done(); + }); + backend.expectOne('/test').flush(body); + }); + if (typeof Blob !== 'undefined') { + it('for a blob', done => { + const body = new Blob([new ArrayBuffer(4)]); + client.get('/test', {responseType: 'blob'}).subscribe(res => { + expect(res).to.eq(body); + done(); + }); + backend.expectOne('/test').flush(body); + }); + } + it('that returns a response', done => { + const body = {'data': 'hello world'}; + client.get('/test', {observe: 'response'}).subscribe(res => { + expect(res instanceof HttpResponse).to.equal(true); + expect(res.body).to.eq(body); + done(); + }); + backend.expectOne('/test').flush(body); + }); + it('that returns a stream of events', done => { + client.get('/test', {observe: 'events'}).pipe(toArray()).toPromise().then(events => { + expect(events.length).to.eq(2); + let x = HttpResponse; + expect(events[0].type).to.eq(HttpEventType.Sent); + expect(events[1].type).to.eq(HttpEventType.Response); + expect(events[1] instanceof HttpResponse).to.be.ok; + done(); + }); + backend.expectOne('/test').flush({'data': 'hello world'}); + }); + it('with progress events enabled', done => { + client.get('/test', {reportProgress: true}).subscribe(() => done()); + const req = backend.expectOne('/test'); + expect(req.request.reportProgress).to.be.true; + req.flush({}); + }); + }); + describe('makes a POST request', () => { + it('with text data', done => { + client.post('/test', 'text body', {observe: 'response', responseType: 'text'}) + .subscribe(res => { + expect(res.ok).to.be.ok; + expect(res.status).to.equal(200); + done(); + }); + backend.expectOne('/test').flush('hello world'); + }); + it('with json data', done => { + const body = {data: 'json body'}; + client.post('/test', body, {observe: 'response', responseType: 'text'}).subscribe(res => { + expect(res.ok).to.be.ok; + expect(res.status).to.equal(200); + done(); + }); + const testReq = backend.expectOne('/test'); + expect(testReq.request.body).to.equal(body); + testReq.flush('hello world'); + }); + it('with a json body of false', done => { + client.post('/test', false, {observe: 'response', responseType: 'text'}).subscribe(res => { + expect(res.ok).to.be.ok; + expect(res.status).to.equal(200); + done(); + }); + const testReq = backend.expectOne('/test'); + expect(testReq.request.body).to.equal(false); + testReq.flush('hello world'); + }); + it('with a json body of 0', done => { + client.post('/test', 0, {observe: 'response', responseType: 'text'}).subscribe(res => { + expect(res.ok).to.be.ok; + expect(res.status).to.equal(200); + done(); + }); + const testReq = backend.expectOne('/test'); + expect(testReq.request.body).to.equal(0); + testReq.flush('hello world'); + }); + it('with an arraybuffer', done => { + const body = new ArrayBuffer(4); + client.post('/test', body, {observe: 'response', responseType: 'text'}).subscribe(res => { + expect(res.ok).to.be.ok; + expect(res.status).to.be.equal(200); + done(); + }); + const testReq = backend.expectOne('/test'); + expect(testReq.request.body).to.equal(body); + testReq.flush('hello world'); + }); + }); + describe('makes a JSONP request', () => { + it('with properly set method and callback', done => { + client.jsonp('/test', 'myCallback').subscribe(() => done()); + backend.expectOne({method: 'JSONP', url: '/test?myCallback=JSONP_CALLBACK'}) + .flush('hello world'); + }); + }); + describe('makes a request for an error response', () => { + it('with a JSON body', done => { + client.get('/test').subscribe(() => {}, (res: HttpErrorResponse) => { + expect(res.error.data).to.equal('hello world'); + done(); + }); + backend.expectOne('/test').flush( + {'data': 'hello world'}, {status: 500, statusText: 'Server error'}); + }); + }); +}); \ No newline at end of file diff --git a/packages/http/src/test/headers.test.ts b/packages/http/src/test/headers.test.ts new file mode 100644 index 00000000..2905913c --- /dev/null +++ b/packages/http/src/test/headers.test.ts @@ -0,0 +1,144 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {describe, it} from 'razmin'; +import {expect} from 'chai'; + +import {HttpHeaders} from '../headers'; + +describe('HttpHeaders', () => { + + describe('initialization', () => { + it('should conform to spec', () => { + const httpHeaders = { + 'Content-Type': 'image/jpeg', + 'Accept-Charset': 'utf-8', + 'X-My-Custom-Header': 'Zeke are cool', + }; + const secondHeaders = new HttpHeaders(httpHeaders); + expect(secondHeaders.get('Content-Type')).to.equal('image/jpeg'); + }); + + it('should merge values in provided dictionary', () => { + const headers = new HttpHeaders({'foo': 'bar'}); + expect(headers.get('foo')).to.equal('bar'); + expect(headers.getAll('foo')).to.eql(['bar']); + }); + + it('should lazily append values', () => { + const src = new HttpHeaders(); + const a = src.append('foo', 'a'); + const b = a.append('foo', 'b'); + const c = b.append('foo', 'c'); + expect(src.getAll('foo')).to.be.null; + expect(a.getAll('foo')).to.eql(['a']); + expect(b.getAll('foo')).to.eql(['a', 'b']); + expect(c.getAll('foo')).to.eql(['a', 'b', 'c']); + }); + + it('should keep the last value when initialized from an object', () => { + const headers = new HttpHeaders({ + 'foo': 'first', + 'fOo': 'second', + }); + + expect(headers.getAll('foo')).to.eql(['second']); + }); + }); + + describe('.set()', () => { + it('should clear all values and re-set for the provided key', () => { + const headers = new HttpHeaders({'foo': 'bar'}); + expect(headers.get('foo')).to.equal('bar'); + + const second = headers.set('foo', 'baz'); + expect(second.get('foo')).to.equal('baz'); + + const third = headers.set('fOO', 'bat'); + expect(third.get('foo')).to.equal('bat'); + }); + + it('should preserve the case of the first call', () => { + const headers = new HttpHeaders(); + const second = headers.set('fOo', 'baz'); + const third = second.set('foo', 'bat'); + expect(third.keys()).to.eql(['fOo']); + }); + }); + + describe('.get()', () => { + it('should be case insensitive', () => { + const headers = new HttpHeaders({'foo': 'baz'}); + expect(headers.get('foo')).to.equal('baz'); + expect(headers.get('FOO')).to.equal('baz'); + }); + + it('should return null if the header is not present', () => { + const headers = new HttpHeaders({bar: []}); + expect(headers.get('bar')).to.equal(null); + expect(headers.get('foo')).to.equal(null); + }); + }); + + describe('.getAll()', () => { + it('should be case insensitive', () => { + const headers = new HttpHeaders({foo: ['bar', 'baz']}); + expect(headers.getAll('foo')).to.eql(['bar', 'baz']); + expect(headers.getAll('FOO')).to.eql(['bar', 'baz']); + }); + + it('should return null if the header is not present', () => { + const headers = new HttpHeaders(); + expect(headers.getAll('foo')).to.equal(null); + }); + }); + + describe('.delete', () => { + it('should be case insensitive', () => { + const headers = new HttpHeaders({'foo': 'baz'}); + expect(headers.has('foo')).to.equal(true); + const second = headers.delete('foo'); + expect(second.has('foo')).to.equal(false); + + const third = second.set('foo', 'baz'); + expect(third.has('foo')).to.equal(true); + const fourth = third.delete('FOO'); + expect(fourth.has('foo')).to.equal(false); + }); + }); + + describe('.append', () => { + it('should append a value to the list', () => { + const headers = new HttpHeaders(); + const second = headers.append('foo', 'bar'); + const third = second.append('foo', 'baz'); + expect(third.get('foo')).to.equal('bar'); + expect(third.getAll('foo')).to.eql(['bar', 'baz']); + }); + + it('should preserve the case of the first call', () => { + const headers = new HttpHeaders(); + const second = headers.append('FOO', 'bar'); + const third = second.append('foo', 'baz'); + expect(third.keys()).to.eql(['FOO']); + }); + }); + + describe('response header strings', () => { + it('should be parsed by the constructor', () => { + const response = `Date: Fri, 20 Nov 2015 01:45:26 GMT\n` + + `Content-Type: application/json; charset=utf-8\n` + + `Transfer-Encoding: chunked\n` + + `Connection: keep-alive`; + const headers = new HttpHeaders(response); + expect(headers.get('Date')).to.equal('Fri, 20 Nov 2015 01:45:26 GMT'); + expect(headers.get('Content-Type')).to.equal('application/json; charset=utf-8'); + expect(headers.get('Transfer-Encoding')).to.equal('chunked'); + expect(headers.get('Connection')).to.equal('keep-alive'); + }); + }); +}); \ No newline at end of file diff --git a/packages/http/src/test/module.test.ts b/packages/http/src/test/module.test.ts new file mode 100644 index 00000000..7f846e42 --- /dev/null +++ b/packages/http/src/test/module.test.ts @@ -0,0 +1,124 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved.JsonpCallbackContext + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {describe, it, beforeEach} from 'razmin'; +import {expect} from 'chai'; + +import {Injectable, Module, Injector} from '@alterior/di'; + +//import {TestBed} from '@angular/core/testing'; +import {Observable} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {Application} from '@alterior/runtime'; +import {HttpHandler} from '../backend'; +import {HttpClient} from '../client'; +import {HTTP_INTERCEPTORS, HttpInterceptor} from '../interceptor'; +import {HttpRequest} from '../request'; +import {HttpEvent, HttpResponse} from '../response'; +import {HttpTestingController} from '../testing/api'; +import {HttpClientTestingModule} from '../testing/module'; +import {TestRequest} from '../testing/request'; + +class TestInterceptor implements HttpInterceptor { + constructor(private value: string) {} + + intercept(req: HttpRequest, delegate: HttpHandler): Observable> { + const existing = req.headers.get('Intercepted'); + const next = !!existing ? existing + ',' + this.value : this.value; + req = req.clone({setHeaders: {'Intercepted': next}}); + return delegate.handle(req).pipe(map(event => { + if (event instanceof HttpResponse) { + const existing = event.headers.get('Intercepted'); + const next = !!existing ? existing + ',' + this.value : this.value; + return event.clone({headers: event.headers.set('Intercepted', next)}); + } + return event; + })); + } +} + +class InterceptorA extends TestInterceptor { + constructor() { super('A'); } +} + +class InterceptorB extends TestInterceptor { + constructor() { super('B'); } +} + +@Injectable() +class ReentrantInterceptor implements HttpInterceptor { + constructor(private client: HttpClient) {} + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return next.handle(req); + } +} + +{ + describe('HttpClientModule', async () => { + let injector: Injector; + beforeEach(async () => { + @Module({ + imports: [HttpClientTestingModule], + providers: [ + {provide: HTTP_INTERCEPTORS, useClass: InterceptorA, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: InterceptorB, multi: true} + ] + }) + class TestModule { + + } + + let app = await Application.bootstrap(TestModule); + + injector = app.injector + }); + it('initializes HttpClient properly', done => { + injector.get(HttpClient).get('/test', {responseType: 'text'}).subscribe(value => { + expect(value).to.equal('ok!'); + done(); + }); + + let thing = injector.get(HttpTestingController); + injector.get(HttpTestingController).expectOne('/test').flush('ok!'); + }); + + it('intercepts outbound responses in the order in which interceptors were bound', done => { + injector.get(HttpClient) + .get('/test', {observe: 'response', responseType: 'text'}) + .subscribe(value => done()); + const req = injector.get(HttpTestingController).expectOne('/test') as TestRequest; + expect(req.request.headers.get('Intercepted')).to.equal('A,B'); + req.flush('ok!'); + }); + it('intercepts inbound responses in the right (reverse binding) order', done => { + injector.get(HttpClient) + .get('/test', {observe: 'response', responseType: 'text'}) + .subscribe(value => { + expect(value.headers.get('Intercepted')).to.equal('B,A'); + done(); + }); + injector.get(HttpTestingController).expectOne('/test').flush('ok!'); + }); + it('allows interceptors to inject HttpClient', async (done) => { + + @Module({ + imports: [HttpClientTestingModule], + providers: [ + {provide: HTTP_INTERCEPTORS, useClass: ReentrantInterceptor, multi: true}, + ], + }) + class TestModule {} + + let app = await Application.bootstrap(TestModule); + let injector = app.injector; + + injector.get(HttpClient).get('/test').subscribe(() => { done(); }); + injector.get(HttpTestingController).expectOne('/test').flush('ok!'); + }); + }); +} diff --git a/packages/http/src/test/params.test.ts b/packages/http/src/test/params.test.ts new file mode 100644 index 00000000..70ae90b4 --- /dev/null +++ b/packages/http/src/test/params.test.ts @@ -0,0 +1,71 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {describe, it} from 'razmin'; +import {expect} from 'chai'; + +import {HttpParams} from '../params'; + +describe('HttpUrlEncodedParams', () => { + describe('initialization', () => { + it('should be empty at construction', () => { + const body = new HttpParams(); + expect(body.toString()).to.equal(''); + }); + + it('should parse an existing url', () => { + const body = new HttpParams({fromString: 'a=b&c=d&c=e'}); + expect(body.getAll('a')).to.eql(['b']); + expect(body.getAll('c')).to.eql(['d', 'e']); + }); + }); + + describe('lazy mutation', () => { + it('should allow setting parameters', () => { + const body = new HttpParams({fromString: 'a=b'}); + const mutated = body.set('a', 'c'); + expect(mutated.toString()).to.equal('a=c'); + }); + + it('should allow appending parameters', () => { + const body = new HttpParams({fromString: 'a=b'}); + const mutated = body.append('a', 'c'); + expect(mutated.toString()).to.equal('a=b&a=c'); + }); + + it('should allow deletion of parameters', () => { + const body = new HttpParams({fromString: 'a=b&c=d&e=f'}); + const mutated = body.delete('c'); + expect(mutated.toString()).to.equal('a=b&e=f'); + }); + + it('should allow chaining of mutations', () => { + const body = new HttpParams({fromString: 'a=b&c=d&e=f'}); + const mutated = body.append('e', 'y').delete('c').set('a', 'x').append('e', 'z'); + expect(mutated.toString()).to.equal('a=x&e=f&e=y&e=z'); + }); + + it('should allow deletion of one value of a parameter', () => { + const body = new HttpParams({fromString: 'a=1&a=2&a=3&a=4&a=5'}); + const mutated = body.delete('a', '2').delete('a', '4'); + expect(mutated.getAll('a')).to.eql(['1', '3', '5']); + }); + }); + + describe('read operations', () => { + it('should give null if parameter is not set', () => { + const body = new HttpParams({fromString: 'a=b&c=d'}); + expect(body.get('e')).to.be.null; + expect(body.getAll('e')).to.be.null; + }); + + it('should give an accurate list of keys', () => { + const body = new HttpParams({fromString: 'a=1&b=2&c=3&d=4'}); + expect(body.keys()).to.eql(['a', 'b', 'c', 'd']); + }); + }); +}); diff --git a/packages/http/src/test/request.test.ts b/packages/http/src/test/request.test.ts new file mode 100644 index 00000000..2bf03f64 --- /dev/null +++ b/packages/http/src/test/request.test.ts @@ -0,0 +1,165 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {describe, it} from 'razmin'; +import {expect} from 'chai'; + +import {HttpHeaders} from '../headers'; +import {HttpParams} from '../params'; +import {HttpRequest} from '../request'; + +const TEST_URL = 'http://angular.io'; +const TEST_STRING = `I'm a body!`; + +{ + describe('HttpRequest', () => { + describe('constructor', () => { + it('initializes url', () => { + const req = new HttpRequest('', TEST_URL, null); + expect(req.url).to.equal(TEST_URL); + }); + it('doesn\'t require a body for body-less methods', () => { + let req = new HttpRequest('GET', TEST_URL); + expect(req.method).to.equal('GET'); + expect(req.body).to.be.null; + req = new HttpRequest('HEAD', TEST_URL); + expect(req.method).to.equal('HEAD'); + expect(req.body).to.be.null; + req = new HttpRequest('JSONP', TEST_URL); + expect(req.method).to.equal('JSONP'); + expect(req.body).to.be.null; + req = new HttpRequest('OPTIONS', TEST_URL); + expect(req.method).to.equal('OPTIONS'); + expect(req.body).to.be.null; + }); + it('accepts a string request method', () => { + const req = new HttpRequest('TEST', TEST_URL, null); + expect(req.method).to.equal('TEST'); + }); + it('accepts a string body', () => { + const req = new HttpRequest('POST', TEST_URL, TEST_STRING); + expect(req.body).to.equal(TEST_STRING); + }); + it('accepts an object body', () => { + const req = new HttpRequest('POST', TEST_URL, {data: TEST_STRING}); + expect(req.body).to.eql({data: TEST_STRING}); + }); + it('creates default headers if not passed', () => { + const req = new HttpRequest('GET', TEST_URL); + expect(req.headers instanceof HttpHeaders).to.be.ok; + }); + it('uses the provided headers if passed', () => { + const headers = new HttpHeaders(); + const req = new HttpRequest('GET', TEST_URL, {headers}); + expect(req.headers).to.equal(headers); + }); + it('defaults to Json', () => { + const req = new HttpRequest('GET', TEST_URL); + expect(req.responseType).to.equal('json'); + }); + }); + describe('clone() copies the request', () => { + const headers = new HttpHeaders({ + 'Test': 'Test header', + }); + const req = new HttpRequest('POST', TEST_URL, 'test body', { + headers, + reportProgress: true, + responseType: 'text', + withCredentials: true, + }); + it('in the base case', () => { + const clone = req.clone(); + expect(clone.method).to.equal('POST'); + expect(clone.responseType).to.equal('text'); + expect(clone.url).to.equal(TEST_URL); + // Headers should be the same, as the headers are sealed. + expect(clone.headers).to.equal(headers); + expect(clone.headers.get('Test')).to.equal('Test header'); + }); + it('and updates the url', + () => { expect(req.clone({url: '/changed'}).url).to.equal('/changed'); }); + it('and updates the method', + () => { expect(req.clone({method: 'PUT'}).method).to.equal('PUT'); }); + it('and updates the body', + () => { expect(req.clone({body: 'changed body'}).body).to.equal('changed body'); }); + }); + describe('content type detection', () => { + const baseReq = new HttpRequest('POST', '/test', null); + it('handles a null body', () => { expect(baseReq.detectContentTypeHeader()).to.be.null; }); + it('doesn\'t associate a content type with ArrayBuffers', () => { + const req = baseReq.clone({body: new ArrayBuffer(4)}); + expect(req.detectContentTypeHeader()).to.be.null; + }); + it('handles strings as text', () => { + const req = baseReq.clone({body: 'hello world'}); + expect(req.detectContentTypeHeader()).to.equal('text/plain'); + }); + it('handles arrays as json', () => { + const req = baseReq.clone({body: ['a', 'b']}); + expect(req.detectContentTypeHeader()).to.equal('application/json'); + }); + it('handles numbers as json', () => { + const req = baseReq.clone({body: 314159}); + expect(req.detectContentTypeHeader()).to.equal('application/json'); + }); + it('handles objects as json', () => { + const req = baseReq.clone({body: {data: 'test data'}}); + expect(req.detectContentTypeHeader()).to.equal('application/json'); + }); + }); + describe('body serialization', () => { + const baseReq = new HttpRequest('POST', '/test', null); + it('handles a null body', () => { expect(baseReq.serializeBody()).to.be.null; }); + it('passes ArrayBuffers through', () => { + const body = new ArrayBuffer(4); + expect(baseReq.clone({body}).serializeBody()).to.equal(body); + }); + it('passes strings through', () => { + const body = 'hello world'; + expect(baseReq.clone({body}).serializeBody()).to.equal(body); + }); + it('serializes arrays as json', () => { + expect(baseReq.clone({body: ['a', 'b']}).serializeBody()).to.equal('["a","b"]'); + }); + it('handles numbers as json', + () => { expect(baseReq.clone({body: 314159}).serializeBody()).to.equal('314159'); }); + it('handles objects as json', () => { + const req = baseReq.clone({body: {data: 'test data'}}); + expect(req.serializeBody()).to.equal('{"data":"test data"}'); + }); + it('serializes parameters as urlencoded', () => { + const params = new HttpParams().append('first', 'value').append('second', 'other'); + const withParams = baseReq.clone({body: params}); + expect(withParams.serializeBody()).to.equal('first=value&second=other'); + expect(withParams.detectContentTypeHeader()) + .to.equal('application/x-www-form-urlencoded;charset=UTF-8'); + }); + }); + describe('parameter handling', () => { + const baseReq = new HttpRequest('GET', '/test', null); + const params = new HttpParams({fromString: 'test=true'}); + it('appends parameters to a base URL', () => { + const req = baseReq.clone({params}); + expect(req.urlWithParams).to.equal('/test?test=true'); + }); + it('appends parameters to a URL with an empty query string', () => { + const req = baseReq.clone({params, url: '/test?'}); + expect(req.urlWithParams).to.equal('/test?test=true'); + }); + it('appends parameters to a URL with a query string', () => { + const req = baseReq.clone({params, url: '/test?other=false'}); + expect(req.urlWithParams).to.equal('/test?other=false&test=true'); + }); + it('sets parameters via setParams', () => { + const req = baseReq.clone({setParams: {'test': 'false'}}); + expect(req.urlWithParams).to.equal('/test?test=false'); + }); + }); + }); +} \ No newline at end of file diff --git a/packages/http/src/test/response.test.ts b/packages/http/src/test/response.test.ts new file mode 100644 index 00000000..d552811c --- /dev/null +++ b/packages/http/src/test/response.test.ts @@ -0,0 +1,82 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {describe, it} from 'razmin'; +import {expect} from 'chai'; + +import {HttpHeaders} from '../headers'; +import {HttpResponse} from '../response'; + +{ + describe('HttpResponse', () => { + describe('constructor()', () => { + it('fully constructs responses', () => { + const resp = new HttpResponse({ + body: 'test body', + headers: new HttpHeaders({ + 'Test': 'Test header', + }), + status: 201, + statusText: 'Created', + url: '/test', + }); + expect(resp.body).to.equal('test body'); + expect(resp.headers instanceof HttpHeaders).to.be.ok; + expect(resp.headers.get('Test')).to.equal('Test header'); + expect(resp.status).to.equal(201); + expect(resp.statusText).to.equal('Created'); + expect(resp.url).to.equal('/test'); + }); + it('uses defaults if no args passed', () => { + const resp = new HttpResponse({}); + expect(resp.headers).not.to.be.null; + expect(resp.status).to.equal(200); + expect(resp.statusText).to.equal('OK'); + expect(resp.body).to.be.null; + expect(resp.ok).to.be.ok; + expect(resp.url).to.be.null; + }); + it('accepts a falsy body', () => { + expect(new HttpResponse({body: false}).body).to.equal(false); + expect(new HttpResponse({body: 0}).body).to.equal(0); + }); + }); + it('.ok is determined by status', () => { + const good = new HttpResponse({status: 200}); + const alsoGood = new HttpResponse({status: 299}); + const badHigh = new HttpResponse({status: 300}); + const badLow = new HttpResponse({status: 199}); + expect(good.ok).to.equal(true); + expect(alsoGood.ok).to.equal(true); + expect(badHigh.ok).to.equal(false); + expect(badLow.ok).to.equal(false); + }); + describe('.clone()', () => { + it('copies the original when given no arguments', () => { + const clone = + new HttpResponse({body: 'test', status: 201, statusText: 'created', url: '/test'}) + .clone(); + expect(clone.body).to.equal('test'); + expect(clone.status).to.equal(201); + expect(clone.statusText).to.equal('created'); + expect(clone.url).to.equal('/test'); + expect(clone.headers).not.to.be.null; + }); + it('overrides the original', () => { + const orig = + new HttpResponse({body: 'test', status: 201, statusText: 'created', url: '/test'}); + const clone = + orig.clone({body: {data: 'test'}, status: 200, statusText: 'Okay', url: '/bar'}); + expect(clone.body).to.eql({data: 'test'}); + expect(clone.status).to.equal(200); + expect(clone.statusText).to.equal('Okay'); + expect(clone.url).to.equal('/bar'); + expect(clone.headers).to.equal(orig.headers); + }); + }); + }); +} \ No newline at end of file diff --git a/packages/http/src/test/test.ts b/packages/http/src/test/test.ts new file mode 100644 index 00000000..c1ea4418 --- /dev/null +++ b/packages/http/src/test/test.ts @@ -0,0 +1,16 @@ +import "reflect-metadata"; +import { suite } from "razmin"; + +suite() + .include([ + '**/client.test.js', + '**/headers.test.js', + '**/module.test.js', + '**/params.test.js', + '**/request.test.js', + '**/response.test.js', + '**/xhr.test.js', + '**/xsrf.test.js', + ]) + .run() +; \ No newline at end of file diff --git a/packages/http/src/test/xhr.mock.ts b/packages/http/src/test/xhr.mock.ts new file mode 100644 index 00000000..a7d1d131 --- /dev/null +++ b/packages/http/src/test/xhr.mock.ts @@ -0,0 +1,121 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {HttpHeaders} from '../headers'; +import {XhrFactory} from '../xhr'; + +export class MockXhrFactory implements XhrFactory { + // TODO(issue/24571): remove '!'. + mock !: MockXMLHttpRequest; + + build(): XMLHttpRequest { return (this.mock = new MockXMLHttpRequest()) as any; } +} + +export class MockXMLHttpRequestUpload { + constructor(private mock: MockXMLHttpRequest) {} + + addEventListener(event: 'progress', handler: Function) { + this.mock.addEventListener('uploadProgress', handler); + } + + removeEventListener(event: 'progress', handler: Function) { + this.mock.removeEventListener('uploadProgress'); + } +} + +export class MockXMLHttpRequest { + // Set by method calls. + body: any; + // TODO(issue/24571): remove '!'. + method !: string; + // TODO(issue/24571): remove '!'. + url !: string; + mockHeaders: {[key: string]: string} = {}; + mockAborted: boolean = false; + + // Directly settable interface. + withCredentials: boolean = false; + responseType: string = 'text'; + + // Mocked response interface. + response: any|undefined = undefined; + responseText: string|undefined = undefined; + responseURL: string|null = null; + status: number = 0; + statusText: string = ''; + mockResponseHeaders: string = ''; + + listeners: { + error?: (event: ErrorEvent) => void, + load?: () => void, + progress?: (event: ProgressEvent) => void, + uploadProgress?: (event: ProgressEvent) => void, + } = {}; + + upload = new MockXMLHttpRequestUpload(this); + + open(method: string, url: string): void { + this.method = method; + this.url = url; + } + + send(body: any): void { this.body = body; } + + addEventListener(event: 'error'|'load'|'progress'|'uploadProgress', handler: Function): void { + this.listeners[event] = handler as any; + } + + removeEventListener(event: 'error'|'load'|'progress'|'uploadProgress'): void { + delete this.listeners[event]; + } + + setRequestHeader(name: string, value: string): void { this.mockHeaders[name] = value; } + + getAllResponseHeaders(): string { return this.mockResponseHeaders; } + + getResponseHeader(header: string): string|null { + return new HttpHeaders(this.mockResponseHeaders).get(header); + } + + mockFlush(status: number, statusText: string, body?: string) { + if (typeof body === 'string') { + this.responseText = body; + } else { + this.response = body; + } + this.status = status; + this.statusText = statusText; + this.mockLoadEvent(); + } + + mockDownloadProgressEvent(loaded: number, total?: number): void { + if (this.listeners.progress) { + this.listeners.progress({ lengthComputable: total !== undefined, loaded, total } as any); + } + } + + mockUploadProgressEvent(loaded: number, total?: number) { + if (this.listeners.uploadProgress) { + this.listeners.uploadProgress( + { lengthComputable: total !== undefined, loaded, total, } as any); + } + } + + mockLoadEvent(): void { + if (this.listeners.load) { + this.listeners.load(); + } + } + + mockErrorEvent(error: any): void { + if (this.listeners.error) { + this.listeners.error(error); + } + } + + abort() { this.mockAborted = true; } +} \ No newline at end of file diff --git a/packages/http/src/test/xhr.test.ts b/packages/http/src/test/xhr.test.ts new file mode 100644 index 00000000..13c8cf8a --- /dev/null +++ b/packages/http/src/test/xhr.test.ts @@ -0,0 +1,352 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {describe, it, beforeEach} from 'razmin'; +import {expect} from 'chai'; + +import {Observable} from 'rxjs'; +import {toArray} from 'rxjs/operators'; + +import {HttpRequest} from '../request'; +import {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpResponse, HttpResponseBase, HttpUploadProgressEvent} from '../response'; +import {HttpXhrBackend} from '../xhr'; + +import {MockXhrFactory} from './xhr.mock'; + +function trackEvents(obs: Observable>): HttpEvent[] { + const events: HttpEvent[] = []; + obs.subscribe(event => events.push(event), err => events.push(err)); + return events; +} + +const TEST_POST = new HttpRequest('POST', '/test', 'some body', { + responseType: 'text', +}); + +const XSSI_PREFIX = ')]}\'\n'; + +{ + describe('XhrBackend', () => { + let factory: MockXhrFactory = null !; + let backend: HttpXhrBackend = null !; + beforeEach(() => { + factory = new MockXhrFactory(); + backend = new HttpXhrBackend(factory); + }); + it('emits status immediately', () => { + const events = trackEvents(backend.handle(TEST_POST)); + expect(events.length).to.equal(1); + expect(events[0].type).to.equal(HttpEventType.Sent); + }); + it('sets method, url, and responseType correctly', () => { + backend.handle(TEST_POST).subscribe(); + expect(factory.mock.method).to.equal('POST'); + expect(factory.mock.responseType).to.equal('text'); + expect(factory.mock.url).to.equal('/test'); + }); + it('sets outgoing body correctly', () => { + backend.handle(TEST_POST).subscribe(); + expect(factory.mock.body).to.equal('some body'); + }); + it('sets outgoing headers, including default headers', () => { + const post = TEST_POST.clone({ + setHeaders: { + 'Test': 'Test header', + }, + }); + backend.handle(post).subscribe(); + expect(factory.mock.mockHeaders).to.eql({ + 'Test': 'Test header', + 'Accept': 'application/json, text/plain, */*', + 'Content-Type': 'text/plain', + }); + }); + it('sets outgoing headers, including overriding defaults', () => { + const setHeaders = { + 'Test': 'Test header', + 'Accept': 'text/html', + 'Content-Type': 'text/css', + }; + backend.handle(TEST_POST.clone({setHeaders})).subscribe(); + expect(factory.mock.mockHeaders).to.eql(setHeaders); + }); + it('passes withCredentials through', () => { + backend.handle(TEST_POST.clone({withCredentials: true})).subscribe(); + expect(factory.mock.withCredentials).to.equal(true); + }); + it('handles a text response', () => { + const events = trackEvents(backend.handle(TEST_POST)); + factory.mock.mockFlush(200, 'OK', 'some response'); + expect(events.length).to.equal(2); + expect(events[1].type).to.equal(HttpEventType.Response); + expect(events[1] instanceof HttpResponse).to.be.ok; + const res = events[1] as HttpResponse; + expect(res.body).to.equal('some response'); + expect(res.status).to.equal(200); + expect(res.statusText).to.equal('OK'); + }); + it('handles a json response', () => { + const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'}))); + factory.mock.mockFlush(200, 'OK', JSON.stringify({data: 'some data'})); + expect(events.length).to.equal(2); + const res = events[1] as HttpResponse<{data: string}>; + expect(res.body !.data).to.equal('some data'); + }); + it('handles a blank json response', () => { + const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'}))); + factory.mock.mockFlush(200, 'OK', ''); + expect(events.length).to.equal(2); + const res = events[1] as HttpResponse<{data: string}>; + expect(res.body).to.be.null; + }); + it('handles a json error response', () => { + const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'}))); + factory.mock.mockFlush(500, 'Error', JSON.stringify({data: 'some data'})); + expect(events.length).to.equal(2); + const res = events[1] as any as HttpErrorResponse; + expect(res.error !.data).to.equal('some data'); + }); + it('handles a json error response with XSSI prefix', () => { + const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'}))); + factory.mock.mockFlush(500, 'Error', XSSI_PREFIX + JSON.stringify({data: 'some data'})); + expect(events.length).to.equal(2); + const res = events[1] as any as HttpErrorResponse; + expect(res.error !.data).to.equal('some data'); + }); + it('handles a json string response', () => { + const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'}))); + expect(factory.mock.responseType).to.equal('text'); + factory.mock.mockFlush(200, 'OK', JSON.stringify('this is a string')); + expect(events.length).to.equal(2); + const res = events[1] as HttpResponse; + expect(res.body).to.equal('this is a string'); + }); + it('handles a json response with an XSSI prefix', () => { + const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'}))); + factory.mock.mockFlush(200, 'OK', XSSI_PREFIX + JSON.stringify({data: 'some data'})); + expect(events.length).to.equal(2); + const res = events[1] as HttpResponse<{data: string}>; + expect(res.body !.data).to.equal('some data'); + }); + it('emits unsuccessful responses via the error path', done => { + backend.handle(TEST_POST).subscribe(undefined, (err: HttpErrorResponse) => { + expect(err instanceof HttpErrorResponse).to.equal(true); + expect(err.error).to.equal('this is the error'); + done(); + }); + factory.mock.mockFlush(400, 'Bad Request', 'this is the error'); + }); + it('emits real errors via the error path', done => { + backend.handle(TEST_POST).subscribe(undefined, (err: HttpErrorResponse) => { + expect(err instanceof HttpErrorResponse).to.equal(true); + expect(err.error instanceof Error); + done(); + }); + factory.mock.mockErrorEvent(new Error('blah')); + }); + describe('progress events', () => { + it('are emitted for download progress', done => { + backend.handle(TEST_POST.clone({reportProgress: true})) + .pipe(toArray()) + .subscribe(events => { + expect(events.map(event => event.type)).to.eql([ + HttpEventType.Sent, + HttpEventType.ResponseHeader, + HttpEventType.DownloadProgress, + HttpEventType.DownloadProgress, + HttpEventType.Response, + ]); + const [progress1, progress2, response] = [ + events[2] as HttpDownloadProgressEvent, events[3] as HttpDownloadProgressEvent, + events[4] as HttpResponse + ]; + expect(progress1.partialText).to.equal('down'); + expect(progress1.loaded).to.equal(100); + expect(progress1.total).to.equal(300); + expect(progress2.partialText).to.equal('download'); + expect(progress2.loaded).to.equal(200); + expect(progress2.total).to.equal(300); + expect(response.body).to.equal('downloaded'); + done(); + }); + factory.mock.responseText = 'down'; + factory.mock.mockDownloadProgressEvent(100, 300); + factory.mock.responseText = 'download'; + factory.mock.mockDownloadProgressEvent(200, 300); + factory.mock.mockFlush(200, 'OK', 'downloaded'); + }); + it('are emitted for upload progress', done => { + backend.handle(TEST_POST.clone({reportProgress: true})) + .pipe(toArray()) + .subscribe(events => { + expect(events.map(event => event.type)).to.eql([ + HttpEventType.Sent, + HttpEventType.UploadProgress, + HttpEventType.UploadProgress, + HttpEventType.Response, + ]); + const [progress1, progress2] = [ + events[1] as HttpUploadProgressEvent, + events[2] as HttpUploadProgressEvent, + ]; + expect(progress1.loaded).to.equal(100); + expect(progress1.total).to.equal(300); + expect(progress2.loaded).to.equal(200); + expect(progress2.total).to.equal(300); + done(); + }); + factory.mock.mockUploadProgressEvent(100, 300); + factory.mock.mockUploadProgressEvent(200, 300); + factory.mock.mockFlush(200, 'OK', 'Done'); + }); + it('are emitted when both upload and download progress are available', done => { + backend.handle(TEST_POST.clone({reportProgress: true})) + .pipe(toArray()) + .subscribe(events => { + expect(events.map(event => event.type)).to.eql([ + HttpEventType.Sent, + HttpEventType.UploadProgress, + HttpEventType.ResponseHeader, + HttpEventType.DownloadProgress, + HttpEventType.Response, + ]); + done(); + }); + factory.mock.mockUploadProgressEvent(100, 300); + factory.mock.mockDownloadProgressEvent(200, 300); + factory.mock.mockFlush(200, 'OK', 'Done'); + }); + it('are emitted even if length is not computable', done => { + backend.handle(TEST_POST.clone({reportProgress: true})) + .pipe(toArray()) + .subscribe(events => { + expect(events.map(event => event.type)).to.eql([ + HttpEventType.Sent, + HttpEventType.UploadProgress, + HttpEventType.ResponseHeader, + HttpEventType.DownloadProgress, + HttpEventType.Response, + ]); + done(); + }); + factory.mock.mockUploadProgressEvent(100); + factory.mock.mockDownloadProgressEvent(200); + factory.mock.mockFlush(200, 'OK', 'Done'); + }); + it('include ResponseHeader with headers and status', done => { + backend.handle(TEST_POST.clone({reportProgress: true})) + .pipe(toArray()) + .subscribe(events => { + expect(events.map(event => event.type)).to.eql([ + HttpEventType.Sent, + HttpEventType.ResponseHeader, + HttpEventType.DownloadProgress, + HttpEventType.Response, + ]); + const partial = events[1] as HttpHeaderResponse; + expect(partial.headers.get('Content-Type')).to.equal('text/plain'); + expect(partial.headers.get('Test')).to.equal('Test header'); + done(); + }); + factory.mock.mockResponseHeaders = 'Test: Test header\nContent-Type: text/plain\n'; + factory.mock.mockDownloadProgressEvent(200); + factory.mock.mockFlush(200, 'OK', 'Done'); + }); + it('are unsubscribed along with the main request', () => { + const sub = backend.handle(TEST_POST.clone({reportProgress: true})).subscribe(); + expect(factory.mock.listeners.progress).not.to.be.undefined; + sub.unsubscribe(); + expect(factory.mock.listeners.progress).to.be.undefined; + }); + it('do not cause headers to be re-parsed on main response', done => { + backend.handle(TEST_POST.clone({reportProgress: true})) + .pipe(toArray()) + .subscribe(events => { + events + .filter( + event => event.type === HttpEventType.Response || + event.type === HttpEventType.ResponseHeader) + .map(event => event as HttpResponseBase) + .forEach(event => { + expect(event.status).to.equal(203); + expect(event.headers.get('Test')).to.equal('This is a test'); + }); + done(); + }); + factory.mock.mockResponseHeaders = 'Test: This is a test\n'; + factory.mock.status = 203; + factory.mock.mockDownloadProgressEvent(100, 300); + factory.mock.mockResponseHeaders = 'Test: should never be read\n'; + factory.mock.mockFlush(203, 'OK', 'Testing 1 2 3'); + }); + }); + describe('gets response URL', () => { + it('from XHR.responsesURL', done => { + backend.handle(TEST_POST).pipe(toArray()).subscribe(events => { + expect(events.length).to.equal(2); + expect(events[1].type).to.equal(HttpEventType.Response); + const response = events[1] as HttpResponse; + expect(response.url).to.equal('/response/url'); + done(); + }); + factory.mock.responseURL = '/response/url'; + factory.mock.mockFlush(200, 'OK', 'Test'); + }); + it('from X-Request-URL header if XHR.responseURL is not present', done => { + backend.handle(TEST_POST).pipe(toArray()).subscribe(events => { + expect(events.length).to.equal(2); + expect(events[1].type).to.equal(HttpEventType.Response); + const response = events[1] as HttpResponse; + expect(response.url).to.equal('/response/url'); + done(); + }); + factory.mock.mockResponseHeaders = 'X-Request-URL: /response/url\n'; + factory.mock.mockFlush(200, 'OK', 'Test'); + }); + it('falls back on Request.url if neither are available', done => { + backend.handle(TEST_POST).pipe(toArray()).subscribe(events => { + expect(events.length).to.equal(2); + expect(events[1].type).to.equal(HttpEventType.Response); + const response = events[1] as HttpResponse; + expect(response.url).to.equal('/test'); + done(); + }); + factory.mock.mockFlush(200, 'OK', 'Test'); + }); + }); + describe('corrects for quirks', () => { + it('by normalizing 1223 status to 204', done => { + backend.handle(TEST_POST).pipe(toArray()).subscribe(events => { + expect(events.length).to.equal(2); + expect(events[1].type).to.equal(HttpEventType.Response); + const response = events[1] as HttpResponse; + expect(response.status).to.equal(204); + done(); + }); + factory.mock.mockFlush(1223, 'IE Special Status', 'Test'); + }); + it('by normalizing 0 status to 200 if a body is present', done => { + backend.handle(TEST_POST).pipe(toArray()).subscribe(events => { + expect(events.length).to.equal(2); + expect(events[1].type).to.equal(HttpEventType.Response); + const response = events[1] as HttpResponse; + expect(response.status).to.equal(200); + done(); + }); + factory.mock.mockFlush(0, 'CORS 0 status', 'Test'); + }); + it('by leaving 0 status as 0 if a body is not present', done => { + backend.handle(TEST_POST).pipe(toArray()).subscribe( + undefined, (error: HttpErrorResponse) => { + expect(error.status).to.equal(0); + done(); + }); + factory.mock.mockFlush(0, 'CORS 0 status'); + }); + }); + }); +} diff --git a/packages/http/src/test/xsrf.test.ts b/packages/http/src/test/xsrf.test.ts new file mode 100644 index 00000000..66027c21 --- /dev/null +++ b/packages/http/src/test/xsrf.test.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {describe, it, beforeEach, afterEach} from 'razmin'; +import {expect} from 'chai'; + +import {HttpHeaders} from '../headers'; +import {HttpRequest} from '../request'; +import {HttpXsrfInterceptor, HttpXsrfTokenExtractor} from '../xsrf'; + +import {HttpClientTestingBackend} from '../testing/backend'; + +class SampleTokenExtractor extends HttpXsrfTokenExtractor { + constructor(private token: string|null) { super(); } + + getToken(): string|null { return this.token; } +} + +{ + describe('HttpXsrfInterceptor', () => { + let backend: HttpClientTestingBackend; + const interceptor = new HttpXsrfInterceptor(new SampleTokenExtractor('test'), 'X-XSRF-TOKEN'); + beforeEach(() => { backend = new HttpClientTestingBackend(); }); + it('applies XSRF protection to outgoing requests', () => { + interceptor.intercept(new HttpRequest('POST', '/test', {}), backend).subscribe(); + const req = backend.expectOne('/test'); + expect(req.request.headers.get('X-XSRF-TOKEN')).to.equal('test'); + req.flush({}); + }); + it('does not apply XSRF protection when request is a GET', () => { + interceptor.intercept(new HttpRequest('GET', '/test'), backend).subscribe(); + const req = backend.expectOne('/test'); + expect(req.request.headers.has('X-XSRF-TOKEN')).to.equal(false); + req.flush({}); + }); + it('does not apply XSRF protection when request is a HEAD', () => { + interceptor.intercept(new HttpRequest('HEAD', '/test'), backend).subscribe(); + const req = backend.expectOne('/test'); + expect(req.request.headers.has('X-XSRF-TOKEN')).to.equal(false); + req.flush({}); + }); + it('does not overwrite existing header', () => { + interceptor + .intercept( + new HttpRequest( + 'POST', '/test', {}, {headers: new HttpHeaders().set('X-XSRF-TOKEN', 'blah')}), + backend) + .subscribe(); + const req = backend.expectOne('/test'); + expect(req.request.headers.get('X-XSRF-TOKEN')).to.equal('blah'); + req.flush({}); + }); + it('does not set the header for a null token', () => { + const interceptor = new HttpXsrfInterceptor(new SampleTokenExtractor(null), 'X-XSRF-TOKEN'); + interceptor.intercept(new HttpRequest('POST', '/test', {}), backend).subscribe(); + const req = backend.expectOne('/test'); + expect(req.request.headers.has('X-XSRF-TOKEN')).to.equal(false); + req.flush({}); + }); + afterEach(() => { backend.verify(); }); + }); +} \ No newline at end of file diff --git a/packages/http/src/testing/api.ts b/packages/http/src/testing/api.ts new file mode 100644 index 00000000..5e1d66d4 --- /dev/null +++ b/packages/http/src/testing/api.ts @@ -0,0 +1,117 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {HttpRequest} from '../request'; + +import {TestRequest} from './request'; + +/** + * Defines a matcher for requests based on URL, method, or both. + * + * + */ +export interface RequestMatch { + method?: string; + url?: string; +} + +/** + * Controller to be injected into tests, that allows for mocking and flushing + * of requests. + * + * + */ +export abstract class HttpTestingController { + /** + * Search for requests that match the given parameter, without any expectations. + */ + abstract match(match: string|RequestMatch|((req: HttpRequest) => boolean)): TestRequest[]; + + /** + * Expect that a single request has been made which matches the given URL, and return its + * mock. + * + * If no such request has been made, or more than one such request has been made, fail with an + * error message including the given request description, if any. + */ + abstract expectOne(url: string, description?: string): TestRequest; + + /** + * Expect that a single request has been made which matches the given parameters, and return + * its mock. + * + * If no such request has been made, or more than one such request has been made, fail with an + * error message including the given request description, if any. + */ + abstract expectOne(params: RequestMatch, description?: string): TestRequest; + + /** + * Expect that a single request has been made which matches the given predicate function, and + * return its mock. + * + * If no such request has been made, or more than one such request has been made, fail with an + * error message including the given request description, if any. + */ + abstract expectOne(matchFn: ((req: HttpRequest) => boolean), description?: string): + TestRequest; + + /** + * Expect that a single request has been made which matches the given condition, and return + * its mock. + * + * If no such request has been made, or more than one such request has been made, fail with an + * error message including the given request description, if any. + */ + abstract expectOne( + match: string|RequestMatch|((req: HttpRequest) => boolean), + description?: string): TestRequest; + + /** + * Expect that no requests have been made which match the given URL. + * + * If a matching request has been made, fail with an error message including the given request + * description, if any. + */ + abstract expectNone(url: string, description?: string): void; + + /** + * Expect that no requests have been made which match the given parameters. + * + * If a matching request has been made, fail with an error message including the given request + * description, if any. + */ + abstract expectNone(params: RequestMatch, description?: string): void; + + /** + * Expect that no requests have been made which match the given predicate function. + * + * If a matching request has been made, fail with an error message including the given request + * description, if any. + */ + abstract expectNone(matchFn: ((req: HttpRequest) => boolean), description?: string): void; + + /** + * Expect that no requests have been made which match the given condition. + * + * If a matching request has been made, fail with an error message including the given request + * description, if any. + */ + abstract expectNone( + match: string|RequestMatch|((req: HttpRequest) => boolean), description?: string): void; + + /** + * Verify that no unmatched requests are outstanding. + * + * If any requests are outstanding, fail with an error message indicating which requests were not + * handled. + * + * If `ignoreCancelled` is not set (the default), `verify()` will also fail if cancelled requests + * were not explicitly matched. + */ + abstract verify(opts?: {ignoreCancelled?: boolean}): void; +} diff --git a/packages/http/src/testing/backend.ts b/packages/http/src/testing/backend.ts new file mode 100644 index 00000000..593eceec --- /dev/null +++ b/packages/http/src/testing/backend.ts @@ -0,0 +1,148 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {HttpEvent, HttpEventType} from '../response'; +import {HttpRequest} from '../request'; +import {HttpBackend} from '../backend'; +import {Injectable} from '@alterior/di'; +import {Observable, Observer} from 'rxjs'; + +import {HttpTestingController, RequestMatch} from './api'; +import {TestRequest} from './request'; + + +/** + * A testing backend for `HttpClient` which both acts as an `HttpBackend` + * and as the `HttpTestingController`. + * + * `HttpClientTestingBackend` works by keeping a list of all open requests. + * As requests come in, they're added to the list. Users can assert that specific + * requests were made and then flush them. In the end, a verify() method asserts + * that no unexpected requests were made. + * + * + */ +@Injectable() +export class HttpClientTestingBackend implements HttpBackend, HttpTestingController { + /** + * List of pending requests which have not yet been expected. + */ + private open: TestRequest[] = []; + + /** + * Handle an incoming request by queueing it in the list of open requests. + */ + handle(req: HttpRequest): Observable> { + return new Observable((observer: Observer) => { + const testReq = new TestRequest(req, observer); + this.open.push(testReq); + observer.next({ type: HttpEventType.Sent } as HttpEvent); + return () => { testReq._cancelled = true; }; + }); + } + + /** + * Helper function to search for requests in the list of open requests. + */ + private _match(match: string|RequestMatch|((req: HttpRequest) => boolean)): TestRequest[] { + if (typeof match === 'string') { + return this.open.filter(testReq => testReq.request.urlWithParams === match); + } else if (typeof match === 'function') { + return this.open.filter(testReq => match(testReq.request)); + } else { + return this.open.filter( + testReq => (!match.method || testReq.request.method === match.method.toUpperCase()) && + (!match.url || testReq.request.urlWithParams === match.url)); + } + } + + /** + * Search for requests in the list of open requests, and return all that match + * without asserting anything about the number of matches. + */ + match(match: string|RequestMatch|((req: HttpRequest) => boolean)): TestRequest[] { + const results = this._match(match); + results.forEach(result => { + const index = this.open.indexOf(result); + if (index !== -1) { + this.open.splice(index, 1); + } + }); + return results; + } + + /** + * Expect that a single outstanding request matches the given matcher, and return + * it. + * + * Requests returned through this API will no longer be in the list of open requests, + * and thus will not match twice. + */ + expectOne(match: string|RequestMatch|((req: HttpRequest) => boolean), description?: string): + TestRequest { + description = description || this.descriptionFromMatcher(match); + const matches = this.match(match); + if (matches.length > 1) { + throw new Error( + `Expected one matching request for criteria "${description}", found ${matches.length} requests.`); + } + if (matches.length === 0) { + throw new Error(`Expected one matching request for criteria "${description}", found none.`); + } + return matches[0]; + } + + /** + * Expect that no outstanding requests match the given matcher, and throw an error + * if any do. + */ + expectNone(match: string|RequestMatch|((req: HttpRequest) => boolean), description?: string): + void { + description = description || this.descriptionFromMatcher(match); + const matches = this.match(match); + if (matches.length > 0) { + throw new Error( + `Expected zero matching requests for criteria "${description}", found ${matches.length}.`); + } + } + + /** + * Validate that there are no outstanding requests. + */ + verify(opts: {ignoreCancelled?: boolean} = {}): void { + let open = this.open; + // It's possible that some requests may be cancelled, and this is expected. + // The user can ask to ignore open requests which have been cancelled. + if (opts.ignoreCancelled) { + open = open.filter(testReq => !testReq.cancelled); + } + if (open.length > 0) { + // Show the methods and URLs of open requests in the error, for convenience. + const requests = open.map(testReq => { + const url = testReq.request.urlWithParams.split('?')[0]; + const method = testReq.request.method; + return `${method} ${url}`; + }) + .join(', '); + throw new Error(`Expected no open requests, found ${open.length}: ${requests}`); + } + } + + private descriptionFromMatcher(matcher: string|RequestMatch| + ((req: HttpRequest) => boolean)): string { + if (typeof matcher === 'string') { + return `Match URL: ${matcher}`; + } else if (typeof matcher === 'object') { + const method = matcher.method || '(any)'; + const url = matcher.url || '(any)'; + return `Match method: ${method}, URL: ${url}`; + } else { + return `Match by function: ${matcher.name}`; + } + } +} diff --git a/packages/http/src/testing/index.ts b/packages/http/src/testing/index.ts new file mode 100644 index 00000000..f3f481f2 --- /dev/null +++ b/packages/http/src/testing/index.ts @@ -0,0 +1,11 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export {HttpTestingController, RequestMatch} from './api'; +export {HttpClientTestingModule} from './module'; +export {TestRequest} from './request'; diff --git a/packages/http/src/testing/module.ts b/packages/http/src/testing/module.ts new file mode 100644 index 00000000..3e3f0106 --- /dev/null +++ b/packages/http/src/testing/module.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {HttpBackend} from '../backend'; +import {HttpClientModule} from '../module'; +import {Module} from '@alterior/di'; + +import {HttpTestingController} from './api'; +import {HttpClientTestingBackend} from './backend'; + + +/** + * Configures `HttpClientTestingBackend` as the `HttpBackend` used by `HttpClient`. + * + * Inject `HttpTestingController` to expect and flush requests in your tests. + * + * + */ +@Module({ + imports: [ + HttpClientModule, + ], + providers: [ + HttpClientTestingBackend, + {provide: HttpBackend, useExisting: HttpClientTestingBackend}, + {provide: HttpTestingController, useExisting: HttpClientTestingBackend}, + ], +}) +export class HttpClientTestingModule { +} diff --git a/packages/http/src/testing/request.test.ts b/packages/http/src/testing/request.test.ts new file mode 100644 index 00000000..4b2ef8e3 --- /dev/null +++ b/packages/http/src/testing/request.test.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {describe, it} from 'razmin'; + +import {HttpClient} from '../../src/client'; +import {HttpClientTestingBackend} from './backend'; +import {expect} from 'chai'; + +describe('HttpClient TestRequest', () => { + it('accepts a null body', () => { + const mock = new HttpClientTestingBackend(); + const client = new HttpClient(mock); + + let resp: any; + client.post('/some-url', {test: 'test'}).subscribe(body => { resp = body; }); + + const req = mock.expectOne('/some-url'); + req.flush(null); + + expect(resp).to.be.null; + }); +}); diff --git a/packages/http/src/testing/request.ts b/packages/http/src/testing/request.ts new file mode 100644 index 00000000..243fbf90 --- /dev/null +++ b/packages/http/src/testing/request.ts @@ -0,0 +1,204 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Observer} from 'rxjs'; +import { HttpRequest } from '../request'; +import { HttpEvent, HttpResponse, HttpErrorResponse } from '../response'; +import { HttpHeaders } from '../headers'; + +/** + * A mock requests that was received and is ready to be answered. + * + * This interface allows access to the underlying `HttpRequest`, and allows + * responding with `HttpEvent`s or `HttpErrorResponse`s. + * + * + */ +export class TestRequest { + /** + * Whether the request was cancelled after it was sent. + */ + get cancelled(): boolean { return this._cancelled; } + + /** + * @internal set by `HttpClientTestingBackend` + */ + _cancelled = false; + + constructor(public request: HttpRequest, private observer: Observer>) {} + + /** + * Resolve the request by returning a body plus additional HTTP information (such as response + * headers) if provided. + * + * Both successful and unsuccessful responses can be delivered via `flush()`. + */ + flush(body: ArrayBuffer|Blob|string|number|Object|(string|number|Object|null)[]|null, opts: { + headers?: HttpHeaders | {[name: string]: string | string[]}, + status?: number, + statusText?: string, + } = {}): void { + if (this.cancelled) { + throw new Error(`Cannot flush a cancelled request.`); + } + const url = this.request.urlWithParams; + const headers = + (opts.headers instanceof HttpHeaders) ? opts.headers : new HttpHeaders(opts.headers); + body = _maybeConvertBody(this.request.responseType, body); + let statusText: string|undefined = opts.statusText; + let status: number = opts.status !== undefined ? opts.status : 200; + if (opts.status === undefined) { + if (body === null) { + status = 204; + statusText = statusText || 'No Content'; + } else { + statusText = statusText || 'OK'; + } + } + if (statusText === undefined) { + throw new Error('statusText is required when setting a custom status.'); + } + if (status >= 200 && status < 300) { + this.observer.next(new HttpResponse({body, headers, status, statusText, url})); + this.observer.complete(); + } else { + this.observer.error(new HttpErrorResponse({error: body, headers, status, statusText, url})); + } + } + + /** + * Resolve the request by returning an `ErrorEvent` (e.g. simulating a network failure). + */ + error(error: ErrorEvent, opts: { + headers?: HttpHeaders | {[name: string]: string | string[]}, + status?: number, + statusText?: string, + } = {}): void { + if (this.cancelled) { + throw new Error(`Cannot return an error for a cancelled request.`); + } + if (opts.status && opts.status >= 200 && opts.status < 300) { + throw new Error(`error() called with a successful status.`); + } + const headers = + (opts.headers instanceof HttpHeaders) ? opts.headers : new HttpHeaders(opts.headers); + this.observer.error(new HttpErrorResponse({ + error, + headers, + status: opts.status || 0, + statusText: opts.statusText || '', + url: this.request.urlWithParams, + })); + } + + /** + * Deliver an arbitrary `HttpEvent` (such as a progress event) on the response stream for this + * request. + */ + event(event: HttpEvent): void { + if (this.cancelled) { + throw new Error(`Cannot send events to a cancelled request.`); + } + this.observer.next(event); + } +} + + +/** + * Helper function to convert a response body to an ArrayBuffer. + */ +function _toArrayBufferBody( + body: ArrayBuffer | Blob | string | number | Object | + (string | number | Object | null)[]): ArrayBuffer { + if (typeof ArrayBuffer === 'undefined') { + throw new Error('ArrayBuffer responses are not supported on this platform.'); + } + if (body instanceof ArrayBuffer) { + return body; + } + throw new Error('Automatic conversion to ArrayBuffer is not supported for response type.'); +} + +/** + * Helper function to convert a response body to a Blob. + */ +function _toBlob( + body: ArrayBuffer | Blob | string | number | Object | + (string | number | Object | null)[]): Blob { + if (typeof Blob === 'undefined') { + throw new Error('Blob responses are not supported on this platform.'); + } + if (body instanceof Blob) { + return body; + } + if (ArrayBuffer && body instanceof ArrayBuffer) { + return new Blob([body]); + } + throw new Error('Automatic conversion to Blob is not supported for response type.'); +} + +/** + * Helper function to convert a response body to JSON data. + */ +function _toJsonBody( + body: ArrayBuffer | Blob | string | number | Object | (string | number | Object | null)[], + format: string = 'JSON'): Object|string|number|(Object | string | number)[] { + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) { + throw new Error(`Automatic conversion to ${format} is not supported for ArrayBuffers.`); + } + if (typeof Blob !== 'undefined' && body instanceof Blob) { + throw new Error(`Automatic conversion to ${format} is not supported for Blobs.`); + } + if (typeof body === 'string' || typeof body === 'number' || typeof body === 'object' || + Array.isArray(body)) { + return body; + } + throw new Error(`Automatic conversion to ${format} is not supported for response type.`); +} + +/** + * Helper function to convert a response body to a string. + */ +function _toTextBody( + body: ArrayBuffer | Blob | string | number | Object | + (string | number | Object | null)[]): string { + if (typeof body === 'string') { + return body; + } + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) { + throw new Error('Automatic conversion to text is not supported for ArrayBuffers.'); + } + if (typeof Blob !== 'undefined' && body instanceof Blob) { + throw new Error('Automatic conversion to text is not supported for Blobs.'); + } + return JSON.stringify(_toJsonBody(body, 'text')); +} + +/** + * Convert a response body to the requested type. + */ +function _maybeConvertBody( + responseType: string, body: ArrayBuffer | Blob | string | number | Object | + (string | number | Object | null)[] | null): ArrayBuffer|Blob|string|number|Object| + (string | number | Object | null)[]|null { + if (body === null) { + return null; + } + switch (responseType) { + case 'arraybuffer': + return _toArrayBufferBody(body); + case 'blob': + return _toBlob(body); + case 'json': + return _toJsonBody(body); + case 'text': + return _toTextBody(body); + default: + throw new Error(`Unsupported responseType: ${responseType}`); + } +} diff --git a/packages/http/src/xhr.ts b/packages/http/src/xhr.ts new file mode 100644 index 00000000..3d26a3a0 --- /dev/null +++ b/packages/http/src/xhr.ts @@ -0,0 +1,338 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Injectable} from '@alterior/di'; +import {Observable, Observer} from 'rxjs'; + +import {HttpBackend} from './backend'; +import {HttpHeaders} from './headers'; +import {HttpRequest} from './request'; +import {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpJsonParseError, HttpResponse, HttpUploadProgressEvent} from './response'; + +const XSSI_PREFIX = /^\)\]\}',?\n/; + +/** + * Determine an appropriate URL for the response, by checking either + * XMLHttpRequest.responseURL or the X-Request-URL header. + */ +function getResponseUrl(xhr: any): string|null { + if ('responseURL' in xhr && xhr.responseURL) { + return xhr.responseURL; + } + if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { + return xhr.getResponseHeader('X-Request-URL'); + } + return null; +} + +/** + * A wrapper around the `XMLHttpRequest` constructor. + * + * + */ +export abstract class XhrFactory { abstract build(): XMLHttpRequest; } + +/** + * A factory for @{link HttpXhrBackend} that uses the `XMLHttpRequest` browser API. + * + * + */ +@Injectable() +export class BrowserXhr implements XhrFactory { + constructor() {} + build(): any { return (new XMLHttpRequest()); } +} + +/** + * Tracks a response from the server that does not yet have a body. + */ +interface PartialResponse { + headers: HttpHeaders; + status: number; + statusText: string; + url: string; +} + +/** + * An `HttpBackend` which uses the XMLHttpRequest API to send + * requests to a backend server. + * + * + */ +@Injectable() +export class HttpXhrBackend implements HttpBackend { + constructor(private xhrFactory: XhrFactory) {} + + /** + * Process a request and return a stream of response events. + */ + handle(req: HttpRequest): Observable> { + // Quick check to give a better error message when a user attempts to use + // HttpClient.jsonp() without installing the JsonpClientModule + if (req.method === 'JSONP') { + throw new Error(`Attempted to construct Jsonp request without JsonpClientModule installed.`); + } + + // Everything happens on Observable subscription. + return new Observable((observer: Observer>) => { + // Start by setting up the XHR object with request method, URL, and withCredentials flag. + const xhr = this.xhrFactory.build(); + xhr.open(req.method, req.urlWithParams); + if (!!req.withCredentials) { + xhr.withCredentials = true; + } + + // Add all the requested headers. + req.headers.forEach((name, values) => xhr.setRequestHeader(name, values.join(','))); + + // Add an Accept header if one isn't present already. + if (!req.headers.has('Accept')) { + xhr.setRequestHeader('Accept', 'application/json, text/plain, */*'); + } + + // Auto-detect the Content-Type header if one isn't present already. + if (!req.headers.has('Content-Type')) { + const detectedType = req.detectContentTypeHeader(); + // Sometimes Content-Type detection fails. + if (detectedType !== null) { + xhr.setRequestHeader('Content-Type', detectedType); + } + } + + // Set the responseType if one was requested. + if (req.responseType) { + const responseType = req.responseType.toLowerCase(); + + // JSON responses need to be processed as text. This is because if the server + // returns an XSSI-prefixed JSON response, the browser will fail to parse it, + // xhr.response will be null, and xhr.responseText cannot be accessed to + // retrieve the prefixed JSON data in order to strip the prefix. Thus, all JSON + // is parsed by first requesting text and then applying JSON.parse. + xhr.responseType = ((responseType !== 'json') ? responseType : 'text') as any; + } + + // Serialize the request body if one is present. If not, this will be set to null. + const reqBody = req.serializeBody(); + + // If progress events are enabled, response headers will be delivered + // in two events - the HttpHeaderResponse event and the full HttpResponse + // event. However, since response headers don't change in between these + // two events, it doesn't make sense to parse them twice. So headerResponse + // caches the data extracted from the response whenever it's first parsed, + // to ensure parsing isn't duplicated. + let headerResponse: HttpHeaderResponse|null = null; + + // partialFromXhr extracts the HttpHeaderResponse from the current XMLHttpRequest + // state, and memoizes it into headerResponse. + const partialFromXhr = (): HttpHeaderResponse => { + if (headerResponse !== null) { + return headerResponse; + } + + // Read status and normalize an IE9 bug (http://bugs.jquery.com/ticket/1450). + const status: number = xhr.status === 1223 ? 204 : xhr.status; + const statusText = xhr.statusText || 'OK'; + + // Parse headers from XMLHttpRequest - this step is lazy. + const headers = new HttpHeaders(xhr.getAllResponseHeaders()); + + // Read the response URL from the XMLHttpResponse instance and fall back on the + // request URL. + const url = getResponseUrl(xhr) || req.url; + + // Construct the HttpHeaderResponse and memoize it. + headerResponse = new HttpHeaderResponse({headers, status, statusText, url}); + return headerResponse; + }; + + // Next, a few closures are defined for the various events which XMLHttpRequest can + // emit. This allows them to be unregistered as event listeners later. + + // First up is the load event, which represents a response being fully available. + const onLoad = () => { + // Read response state from the memoized partial data. + let {headers, status, statusText, url} = partialFromXhr(); + + // The body will be read out if present. + let body: any|null = null; + + if (status !== 204) { + // Use XMLHttpRequest.response if set, responseText otherwise. + body = (typeof xhr.response === 'undefined') ? xhr.responseText : xhr.response; + } + + // Normalize another potential bug (this one comes from CORS). + if (status === 0) { + status = !!body ? 200 : 0; + } + + // ok determines whether the response will be transmitted on the event or + // error channel. Unsuccessful status codes (not 2xx) will always be errors, + // but a successful status code can still result in an error if the user + // asked for JSON data and the body cannot be parsed as such. + let ok = status >= 200 && status < 300; + + // Check whether the body needs to be parsed as JSON (in many cases the browser + // will have done that already). + if (req.responseType === 'json' && typeof body === 'string') { + // Save the original body, before attempting XSSI prefix stripping. + const originalBody = body; + body = body.replace(XSSI_PREFIX, ''); + try { + // Attempt the parse. If it fails, a parse error should be delivered to the user. + body = body !== '' ? JSON.parse(body) : null; + } catch (error) { + // Since the JSON.parse failed, it's reasonable to assume this might not have been a + // JSON response. Restore the original body (including any XSSI prefix) to deliver + // a better error response. + body = originalBody; + + // If this was an error request to begin with, leave it as a string, it probably + // just isn't JSON. Otherwise, deliver the parsing error to the user. + if (ok) { + // Even though the response status was 2xx, this is still an error. + ok = false; + // The parse error contains the text of the body that failed to parse. + body = { error, text: body } as HttpJsonParseError; + } + } + } + + if (ok) { + // A successful response is delivered on the event stream. + observer.next(new HttpResponse({ + body, + headers, + status, + statusText, + url: url || undefined, + })); + // The full body has been received and delivered, no further events + // are possible. This request is complete. + observer.complete(); + } else { + // An unsuccessful request is delivered on the error channel. + observer.error(new HttpErrorResponse({ + // The error in this case is the response body (error from the server). + error: body, + headers, + status, + statusText, + url: url || undefined, + })); + } + }; + + // The onError callback is called when something goes wrong at the network level. + // Connection timeout, DNS error, offline, etc. These are actual errors, and are + // transmitted on the error channel. + const onError = (error: ErrorEvent) => { + const res = new HttpErrorResponse({ + error, + status: xhr.status || 0, + statusText: xhr.statusText || 'Unknown Error', + }); + observer.error(res); + }; + + // The sentHeaders flag tracks whether the HttpResponseHeaders event + // has been sent on the stream. This is necessary to track if progress + // is enabled since the event will be sent on only the first download + // progerss event. + let sentHeaders = false; + + // The download progress event handler, which is only registered if + // progress events are enabled. + const onDownProgress = (event: ProgressEvent) => { + // Send the HttpResponseHeaders event if it hasn't been sent already. + if (!sentHeaders) { + observer.next(partialFromXhr()); + sentHeaders = true; + } + + // Start building the download progress event to deliver on the response + // event stream. + let progressEvent: HttpDownloadProgressEvent = { + type: HttpEventType.DownloadProgress, + loaded: event.loaded, + }; + + // Set the total number of bytes in the event if it's available. + if (event.lengthComputable) { + progressEvent.total = event.total; + } + + // If the request was for text content and a partial response is + // available on XMLHttpRequest, include it in the progress event + // to allow for streaming reads. + if (req.responseType === 'text' && !!xhr.responseText) { + progressEvent.partialText = xhr.responseText; + } + + // Finally, fire the event. + observer.next(progressEvent); + }; + + // The upload progress event handler, which is only registered if + // progress events are enabled. + const onUpProgress = (event: ProgressEvent) => { + // Upload progress events are simpler. Begin building the progress + // event. + let progress: HttpUploadProgressEvent = { + type: HttpEventType.UploadProgress, + loaded: event.loaded, + }; + + // If the total number of bytes being uploaded is available, include + // it. + if (event.lengthComputable) { + progress.total = event.total; + } + + // Send the event. + observer.next(progress); + }; + + // By default, register for load and error events. + xhr.addEventListener('load', onLoad); + xhr.addEventListener('error', onError); + + // Progress events are only enabled if requested. + if (req.reportProgress) { + // Download progress is always enabled if requested. + xhr.addEventListener('progress', onDownProgress); + + // Upload progress depends on whether there is a body to upload. + if (reqBody !== null && xhr.upload) { + xhr.upload.addEventListener('progress', onUpProgress); + } + } + + // Fire the request, and notify the event stream that it was fired. + xhr.send(reqBody); + observer.next({type: HttpEventType.Sent}); + + // This is the return from the Observable function, which is the + // request cancellation handler. + return () => { + // On a cancellation, remove all registered event listeners. + xhr.removeEventListener('error', onError); + xhr.removeEventListener('load', onLoad); + if (req.reportProgress) { + xhr.removeEventListener('progress', onDownProgress); + if (reqBody !== null && xhr.upload) { + xhr.upload.removeEventListener('progress', onUpProgress); + } + } + + // Finally, abort the in-flight request. + xhr.abort(); + }; + }); + } +} diff --git a/packages/http/src/xsrf.ts b/packages/http/src/xsrf.ts new file mode 100644 index 00000000..cf15d2dd --- /dev/null +++ b/packages/http/src/xsrf.ts @@ -0,0 +1,62 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +//import {DOCUMENT, ɵparseCookieValue as parseCookieValue} from '@angular/common'; +import {Inject, Injectable, InjectionToken} from '@alterior/di'; +import {Observable} from 'rxjs'; + +import {HttpHandler} from './backend'; +import {HttpInterceptor} from './interceptor'; +import {HttpRequest} from './request'; +import {HttpEvent} from './response'; + +export const XSRF_COOKIE_NAME = new InjectionToken('XSRF_COOKIE_NAME'); +export const XSRF_HEADER_NAME = new InjectionToken('XSRF_HEADER_NAME'); + +/** + * Retrieves the current XSRF token to use with the next outgoing request. + * + * + */ +export abstract class HttpXsrfTokenExtractor { + /** + * Get the XSRF token to use with an outgoing request. + * + * Will be called for every request, so the token may change between requests. + */ + abstract getToken(): string|null; +} + +/** + * `HttpInterceptor` which adds an XSRF token to eligible outgoing requests. + */ +@Injectable() +export class HttpXsrfInterceptor implements HttpInterceptor { + constructor( + private tokenService: HttpXsrfTokenExtractor, + @Inject(XSRF_HEADER_NAME) private headerName: string) {} + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + const lcUrl = req.url.toLowerCase(); + // Skip both non-mutating requests and absolute URLs. + // Non-mutating requests don't require a token, and absolute URLs require special handling + // anyway as the cookie set + // on our origin is not the same as the token expected by another origin. + if (req.method === 'GET' || req.method === 'HEAD' || lcUrl.startsWith('http://') || + lcUrl.startsWith('https://')) { + return next.handle(req); + } + const token = this.tokenService.getToken(); + + // Be careful not to overwrite an existing header of the same name. + if (token !== null && !req.headers.has(this.headerName)) { + req = req.clone({headers: req.headers.set(this.headerName, token)}); + } + return next.handle(req); + } +} diff --git a/packages/http/tsconfig-build.json b/packages/http/tsconfig-build.json new file mode 100644 index 00000000..39180a20 --- /dev/null +++ b/packages/http/tsconfig-build.json @@ -0,0 +1,25 @@ +{ + "extends": "../tsconfig-build.json", + + "compilerOptions": { + "baseUrl": ".", + "rootDir": "../", + "paths": { + "@angular/common": ["../../../dist/packages/common"], + "@angular/core": ["../../../dist/packages/core"] + }, + "outDir": "../../../dist/packages/common" + }, + + "files": [ + "public_api.ts" + ], + + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "strictMetadataEmit": false, + "skipTemplateCodegen": true, + "flatModuleOutFile": "http.js", + "flatModuleId": "@angular/common/http" + } +} diff --git a/packages/http/tsconfig.json b/packages/http/tsconfig.json index cbeb105d..d78c61f7 100644 --- a/packages/http/tsconfig.json +++ b/packages/http/tsconfig.json @@ -4,5 +4,7 @@ "baseUrl": "./src", "outDir": "./dist" }, - "include": [ "./src/**/*.ts" ] + "include": [ + "./src/**/*.ts" + ] } \ No newline at end of file diff --git a/packages/runtime/package-lock.json b/packages/runtime/package-lock.json index 8937828f..25578e1e 100644 --- a/packages/runtime/package-lock.json +++ b/packages/runtime/package-lock.json @@ -25,6 +25,12 @@ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -48,9 +54,29 @@ "dev": true }, "callsites": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-1.0.1.tgz", - "integrity": "sha1-wUwkGIzo4dagMLTDyULmuolbaho=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, "colors": { @@ -65,12 +91,27 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", @@ -175,6 +216,14 @@ "dev": true, "requires": { "callsites": "^1.0.0" + }, + "dependencies": { + "callsites": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-1.0.1.tgz", + "integrity": "sha1-wUwkGIzo4dagMLTDyULmuolbaho=", + "dev": true + } } }, "path-dirname": { @@ -189,6 +238,12 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -211,25 +266,17 @@ } }, "razmin": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/razmin/-/razmin-0.4.0.tgz", - "integrity": "sha512-iP38gEa7v8qhckNEjsxb5Sckf12t0KAt0IQn9dISD5fjTcHlArLI/bfzcaOxM9CYZLbDyZ/CmbFg5CeUBZADWg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/razmin/-/razmin-0.5.0.tgz", + "integrity": "sha512-W40JFOuquSUpf+fxm+inYZfuV3qR+4iEc64saoOVAyLcjX6CGfSCGtjWBdN3BgJ2LTPzLsAVmMri1fUoJO9koA==", "dev": true, "requires": { "callsites": "^2.0.0", "colors": "^1.1.2", "require-glob": "^3.2.0", - "rxjs": "^5.5.6", + "rxjs": "^6.3.2", "source-map-support": "^0.5.0", "zone.js": "^0.8.20" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - } } }, "reflect-metadata": { @@ -249,12 +296,12 @@ } }, "rxjs": { - "version": "5.5.12", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", - "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz", + "integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==", "dev": true, "requires": { - "symbol-observable": "1.0.1" + "tslib": "^1.9.0" } }, "source-map": { @@ -273,10 +320,16 @@ "source-map": "^0.6.0" } }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "wrappy": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index e8e2ba73..d29333d7 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -32,7 +32,8 @@ }, "devDependencies": { "@types/node": "^10.9.4", - "razmin": "^0.4.0", + "chai": "^4.1.2", + "razmin": "^0.5.0", "require-glob": "^3.2.0" }, "gitHead": "dd889c35cf093b43b0156780f242919f41935b55" diff --git a/packages/runtime/src/application.ts b/packages/runtime/src/application.ts index 13e49c69..fd4d22b6 100644 --- a/packages/runtime/src/application.ts +++ b/packages/runtime/src/application.ts @@ -1,8 +1,8 @@ import 'reflect-metadata'; import { ApplicationOptions, AppOptionsAnnotation } from './app-options'; -import { ReflectiveInjector, Provider, Injectable } from 'injection-js'; -import { ModuleAnnotation } from '@alterior/di'; +import { ReflectiveInjector, Provider, Injectable, + ModuleAnnotation } from '@alterior/di'; import { Runtime } from './modules'; import { ApplicationArgs } from './args'; @@ -118,7 +118,17 @@ export class Application { ); providers.push(Application); - let injector = ReflectiveInjector.resolveAndCreate(providers); + let injector : ReflectiveInjector; + try { + injector = ReflectiveInjector.resolveAndCreate(providers); + } catch (e) { + console.error(`Failed to resolve injector.`); + console.error(`Providers:`); + console.dir(providers); + console.error(`Modules:`); + console.dir(runtime.definitions); + throw e; + } runtime.load(injector); runtime.fireEvent('OnInit'); diff --git a/packages/runtime/src/module.test.ts b/packages/runtime/src/module.test.ts new file mode 100644 index 00000000..c054434e --- /dev/null +++ b/packages/runtime/src/module.test.ts @@ -0,0 +1,28 @@ +import { describe, it } from 'razmin'; +import { Module, Injector } from '@alterior/di'; +import { Application } from './application'; +import { expect } from 'chai'; + +describe("Modules", () => { + it('allows injection of the Injector', () => { + let sawInjector : Injector = null; + @Module({ + + }) + class TestModule { + constructor(injector : Injector) { + sawInjector = injector; + } + } + + Application.bootstrap(TestModule); + + expect(sawInjector).not.to.be.null; + + let directInjectedInjector = sawInjector.get(Injector); + + expect(directInjectedInjector).not.to.be.null; + expect(directInjectedInjector).to.equal(sawInjector); + + }); +}) \ No newline at end of file diff --git a/packages/runtime/src/modules.ts b/packages/runtime/src/modules.ts index f4174c66..9e2da918 100644 --- a/packages/runtime/src/modules.ts +++ b/packages/runtime/src/modules.ts @@ -1,5 +1,5 @@ import { ModuleAnnotation, ModuleOptions, Module, ModuleLike } from "@alterior/di"; -import { Injector, Provider, ReflectiveInjector } from "injection-js"; +import { Injector, Provider, ReflectiveInjector } from "@alterior/di"; import { timeout } from "@alterior/common"; /** @@ -95,10 +95,23 @@ export class Runtime { return; this._injector = injector; - let ownInjector = ReflectiveInjector.resolveAndCreate( - this.definitions.map(x => x.target), - injector - ); + let ownInjector : ReflectiveInjector; + + let providers = this.definitions.map(x => x.target); + + try { + ownInjector = ReflectiveInjector.resolveAndCreate( + providers, + injector + ); + } catch (e) { + console.error(`Failed to construct injector:`); + console.error(`Providers:`); + console.dir(providers); + console.error(`Definitions:`); + console.dir(this.definitions); + throw e; + } let moduleInstances = this.definitions.map (defn => new ModuleInstance(defn, ownInjector.get(defn.target))); this.instances = moduleInstances; @@ -165,9 +178,12 @@ export class Runtime { this.visited.push(module); // Construct this compilation unit + let isExtension = false; if (module['$module']) { + isExtension = true; // This is a mask + module = Object.assign({}, module); let parentModule = module['$module']; let options : ModuleOptions = Object.assign({}, module as any); delete module['$module']; @@ -179,15 +195,18 @@ export class Runtime { @Module(options) class subModule {}; module = subModule; + + let metadata = this.getMetadataForModule(module); } let metadata = this.getMetadataForModule(module); + this.definitions.push({ target: module, metadata, }); - if (metadata.imports) { + if (metadata && metadata.imports) { for (let importedModule of metadata.imports) { this.resolveModule(importedModule); } diff --git a/packages/web-server/package-lock.json b/packages/web-server/package-lock.json index f0aa5f78..b174cb83 100644 --- a/packages/web-server/package-lock.json +++ b/packages/web-server/package-lock.json @@ -10,12 +10,12 @@ "integrity": "sha1-ZirVGPRWC2esHH7SixN87K3suR0=" }, "@lerna/add": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.3.0.tgz", - "integrity": "sha512-xJ8ekckqSL8n1jbTDHFpV06kq1CpHzroiV6ZrecxD+Y7ogEo4qt0Pul4NbA2SP+9ojwWr9TV4bpyjBkK8oS4ng==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.3.1.tgz", + "integrity": "sha512-9jwrU7gPVLV9uFWosqz6Y1d4S/0WP2uVC7A+wXCdtXjSSypeeF0tfmczgONj1ZWH+vOktTwlBru4ErIAceBTdg==", "dev": true, "requires": { - "@lerna/bootstrap": "^3.3.0", + "@lerna/bootstrap": "^3.3.1", "@lerna/command": "^3.3.0", "@lerna/filter-options": "^3.3.0", "@lerna/npm-conf": "^3.0.0", @@ -39,9 +39,9 @@ } }, "@lerna/bootstrap": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.3.0.tgz", - "integrity": "sha512-CV7muu43ygE1FSbMCTpCboeyZBLO43p6WGaqBekKX7qo1ewD7Bb530cQpwGilQptHu+yJLwBqhMIOIHYnTSi5Q==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.3.1.tgz", + "integrity": "sha512-cN8FBzTvxpOw3tedl0tkJoZaSWE0V0KpAz0xknuSr5LQ1lLoRchIfeVoXlqcfWx64eyE9x84kwzCezsI0Q4VIw==", "dev": true, "requires": { "@lerna/batch-packages": "^3.1.2", @@ -51,7 +51,7 @@ "@lerna/npm-conf": "^3.0.0", "@lerna/npm-install": "^3.3.0", "@lerna/rimraf-dir": "^3.3.0", - "@lerna/run-lifecycle": "^3.3.0", + "@lerna/run-lifecycle": "^3.3.1", "@lerna/run-parallel-batches": "^3.0.0", "@lerna/symlink-binary": "^3.3.0", "@lerna/symlink-dependencies": "^3.3.0", @@ -70,16 +70,16 @@ } }, "@lerna/changed": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.3.0.tgz", - "integrity": "sha512-SQ1QaNPmVAPTGHktH0yvMRGPoXAc5BCh17SVEEweNw+3nHMJ6QxDwrLx8iFIr4BrWvae1hm4OFaInBjWr+4vKg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.3.1.tgz", + "integrity": "sha512-RWX4wRdTbUK1sDQCQnPYWjCm5GzpZ6145B7TrPK8BbD1EZkwA9su0zpdxftWSf0WeaKRMVRetpL/2a4XLgtz2Q==", "dev": true, "requires": { "@lerna/collect-updates": "^3.3.0", "@lerna/command": "^3.3.0", "@lerna/listable": "^3.0.0", "@lerna/output": "^3.0.0", - "@lerna/version": "^3.3.0" + "@lerna/version": "^3.3.1" } }, "@lerna/check-working-tree": { @@ -104,14 +104,14 @@ } }, "@lerna/clean": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.3.0.tgz", - "integrity": "sha512-UDlyI35qQN5m9vi88pJK+eO46RFkJEzGP8hSBpqH2AdgY5DF1Pp6vqnG3nHJ95qRpYbMBWRpmUER1vqiGUDyNg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.3.1.tgz", + "integrity": "sha512-ReJ0QP8hfninuXxMBq3r0PeeDKl6QWgyx2BDiuLM1IdgsdP+hrDHaaUortYBfEucSupu8oqM06ltj1M9FoBs0A==", "dev": true, "requires": { "@lerna/command": "^3.3.0", "@lerna/filter-options": "^3.3.0", - "@lerna/prompt": "^3.0.0", + "@lerna/prompt": "^3.3.1", "@lerna/rimraf-dir": "^3.3.0", "p-map": "^1.2.0", "p-map-series": "^1.0.0", @@ -180,9 +180,9 @@ } }, "@lerna/create": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.3.0.tgz", - "integrity": "sha512-uSuACmJtQbvit3Ev+Zv4FLPUdTHlCeAdq1R+BVHaMqDB6hI4+Xxl4EG5yNUj6uIpI/SYiETiFHG5SOWXohEoBg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.3.1.tgz", + "integrity": "sha512-4VASkTLvN66euTcWMPN2vIzEoP07hgutx7V70CXSOc+DiWV8S22z0PjXATi2yli83TC/Qj4gHYtU2futQrdY1A==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", @@ -200,7 +200,7 @@ "slash": "^1.0.0", "validate-npm-package-license": "^3.0.3", "validate-npm-package-name": "^3.0.0", - "whatwg-url": "^6.5.0" + "whatwg-url": "^7.0.0" } }, "@lerna/create-symlink": { @@ -298,14 +298,14 @@ } }, "@lerna/import": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.3.0.tgz", - "integrity": "sha512-6dgOZEBHQF7yMuXCxUabQa9zxfOY2WOy5idCLIJJd2wx1cA7uVEdsOkjKxnrAt39GCP61pXm2R8gyUQ2VqPiqw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.3.1.tgz", + "integrity": "sha512-2OzTQDkYKbBPpyP2iOI1sWfcvMjNLjjHjmREq/uOWJaSIk5J3Ukt71OPpcOHh4V2CBOlXidCcO+Hyb4FVIy8fw==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", "@lerna/command": "^3.3.0", - "@lerna/prompt": "^3.0.0", + "@lerna/prompt": "^3.3.1", "@lerna/validation-error": "^3.0.0", "dedent": "^0.7.0", "fs-extra": "^7.0.0", @@ -409,9 +409,9 @@ } }, "@lerna/npm-publish": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.3.0.tgz", - "integrity": "sha512-QwWVHNJkfvPneT5Zicg9QxVGyvBs795O0h/yDSn7ofz3Hx8KA6HgmJeWOHzkdTVyUcnF+Nu2Hn8NT2g5Xr23Kg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.3.1.tgz", + "integrity": "sha512-bVTlWIcBL6Zpyzqvr9C7rxXYcoPw+l7IPz5eqQDNREj1R39Wj18OWB2KTJq8l7LIX7Wf4C2A1uT5hJaEf9BuvA==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", @@ -485,19 +485,19 @@ } }, "@lerna/prompt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/prompt/-/prompt-3.0.0.tgz", - "integrity": "sha512-EzvNexDTh//GlpOz68zRo16NdOIqWqiiXMs9tIxpELQubH+kUGKvBSiBrZ2Zyrfd8pQhIf+8qARtkCG+G7wzQQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/prompt/-/prompt-3.3.1.tgz", + "integrity": "sha512-eJhofrUCUaItMIH6et8kI7YqHfhjWqGZoTsE+40NRCfAraOMWx+pDzfRfeoAl3qeRAH2HhNj1bkYn70FbUOxuQ==", "dev": true, "requires": { - "inquirer": "^5.1.0", + "inquirer": "^6.2.0", "npmlog": "^4.1.2" } }, "@lerna/publish": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.3.0.tgz", - "integrity": "sha512-K/vSv6IFgsg1P5dKCKU2/npAzbY3C1/yScv+MwSkOUa+uUKr38fKujXt7mnALvUzs080tejtnj2O3ScEAfhhqQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.3.1.tgz", + "integrity": "sha512-SrpeWhRM4Ysn9dqt7hDch81C0FUUCftY0mIue0t1TvdIopXsObhQS+y1mmCXB/15i1tBpmTMtCvDdS6tt/i4iQ==", "dev": true, "requires": { "@lerna/batch-packages": "^3.1.2", @@ -508,13 +508,13 @@ "@lerna/describe-ref": "^3.3.0", "@lerna/get-npm-exec-opts": "^3.0.0", "@lerna/npm-dist-tag": "^3.3.0", - "@lerna/npm-publish": "^3.3.0", + "@lerna/npm-publish": "^3.3.1", "@lerna/output": "^3.0.0", - "@lerna/prompt": "^3.0.0", - "@lerna/run-lifecycle": "^3.3.0", + "@lerna/prompt": "^3.3.1", + "@lerna/run-lifecycle": "^3.3.1", "@lerna/run-parallel-batches": "^3.0.0", "@lerna/validation-error": "^3.0.0", - "@lerna/version": "^3.3.0", + "@lerna/version": "^3.3.1", "fs-extra": "^7.0.0", "npm-package-arg": "^6.0.0", "npmlog": "^4.1.2", @@ -565,9 +565,9 @@ } }, "@lerna/run-lifecycle": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.3.0.tgz", - "integrity": "sha512-H2a+QgQ6n4IhUxE+YZKR5orEtByUJdWwGJ/6+P+Cji5WqFEOpoR82IuhePyL+m9PBcy8+LlVxif7b+xXS4jaKA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.3.1.tgz", + "integrity": "sha512-xy4K3amlXk0LjSa5d3VqmrW9SsxMyvI7lw2dHDMb5PqjjcjMQgb6+nFbycwyJMhCP8u7MwQIZ4hFYF9XYbWSzQ==", "dev": true, "requires": { "@lerna/npm-conf": "^3.0.0", @@ -623,9 +623,9 @@ } }, "@lerna/version": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.3.0.tgz", - "integrity": "sha512-sQy4d0CFM+yFJSelaaZERDYApL+0GS0aW+kA/BWRBg+vItlrdZcubUcin7G8P9gCe5V5MkTx9sAgB3i3QI3a4g==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.3.1.tgz", + "integrity": "sha512-MqIa56Tkb9XmYTtp1YIAtq9sYeeHxI6P7elvrNcwDfnJzYryJpVeYTsCpqEJ/lHQrmGLMZIoI2BmX4Ua2VJ7AQ==", "dev": true, "requires": { "@lerna/batch-packages": "^3.1.2", @@ -635,8 +635,8 @@ "@lerna/command": "^3.3.0", "@lerna/conventional-commits": "^3.3.0", "@lerna/output": "^3.0.0", - "@lerna/prompt": "^3.0.0", - "@lerna/run-lifecycle": "^3.3.0", + "@lerna/prompt": "^3.3.1", + "@lerna/run-lifecycle": "^3.3.1", "@lerna/validation-error": "^3.0.0", "chalk": "^2.3.1", "dedent": "^0.7.0", @@ -1358,9 +1358,9 @@ "dev": true }, "callsites": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-1.0.1.tgz", - "integrity": "sha1-wUwkGIzo4dagMLTDyULmuolbaho=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true }, "camelcase": { @@ -1415,9 +1415,9 @@ } }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "chokidar": { @@ -1459,9 +1459,9 @@ "dev": true }, "ci-info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.5.0.tgz", - "integrity": "sha512-Bx/xWOzip4whERIvC97aIHjWCa8FxEn0ezng0oVn4kma6p+90Fbs3bTcJw6ZL0da2EPHydxsXJPZxNUv5oWb1Q==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.5.1.tgz", + "integrity": "sha512-fKFIKXaYiL1exImwJ0AhR/6jxFPSKQBk2ayV5NiNoruUs2+rxC2kNw0EG+1Z9dugZRdCrppskQ8DN2cyaUM1Hw==", "dev": true }, "class-utils": { @@ -1683,9 +1683,9 @@ } }, "config-chain": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", - "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", "dev": true, "requires": { "ini": "^1.3.4", @@ -2181,7 +2181,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -2263,9 +2263,9 @@ } }, "es6-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", "dev": true }, "es6-promisify": { @@ -2300,12 +2300,13 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "event-stream": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.5.tgz", - "integrity": "sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz", + "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==", "dev": true, "requires": { "duplexer": "^0.1.1", + "flatmap-stream": "^0.1.0", "from": "^0.1.7", "map-stream": "0.0.7", "pause-stream": "^0.0.11", @@ -2429,14 +2430,25 @@ } }, "external-editor": { - "version": "2.2.0", - "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "extglob": { @@ -2608,6 +2620,12 @@ "locate-path": "^2.0.0" } }, + "flatmap-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.0.tgz", + "integrity": "sha512-Nlic4ZRYxikqnK5rj3YoxDVKGGtUjcNDUtvQ7XsdGLZmMwdUYnXf10o1zcXtzEZTBgc6GxeRpQxV/Wu3WPIIHA==", + "dev": true + }, "flush-write-stream": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", @@ -3697,13 +3715,19 @@ }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, @@ -3819,21 +3843,21 @@ "integrity": "sha512-zHI+E+dM0PXix5FFTO1Y4/UOyAzE7zG1l/QwAn4jchTThOoBq+UYRFK4AVG7lQgFL+go62SbrzSsjXy9DFEZUg==" }, "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", "dev": true, "requires": { "ansi-escapes": "^3.0.0", "chalk": "^2.0.0", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^2.1.0", + "external-editor": "^3.0.0", "figures": "^2.0.0", - "lodash": "^4.3.0", + "lodash": "^4.17.10", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rxjs": "^5.5.2", + "rxjs": "^6.1.0", "string-width": "^2.1.0", "strip-ansi": "^4.0.0", "through": "^2.3.6" @@ -3940,12 +3964,12 @@ } }, "is-ci": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.0.tgz", - "integrity": "sha512-plgvKjQtalH2P3Gytb7L61Lmz95g2DlpzFiQyRSFew8WoJKxtKRzrZMeyRN2supblm3Psc8OQGy7Xjb6XG11jw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { - "ci-info": "^1.3.0" + "ci-info": "^1.5.0" } }, "is-data-descriptor": { @@ -4272,26 +4296,26 @@ } }, "lerna": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.3.0.tgz", - "integrity": "sha512-ZgJFS9OjMA8srdAyz4vJYhbIBejzvjZsCCyUMICHsv9zbIfmXUwERD5O7DwGJCv53i1SxTuW1VV/NNCay9exGQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.3.1.tgz", + "integrity": "sha512-2/lgnuu8HsMlUWf9OGhZuYjtDDZWDIef5GglNHJST0oNLZ8mZEI/k/MQddRqLXI597XUDAh6bK0yfB1aFe1y3w==", "dev": true, "requires": { - "@lerna/add": "^3.3.0", - "@lerna/bootstrap": "^3.3.0", - "@lerna/changed": "^3.3.0", - "@lerna/clean": "^3.3.0", + "@lerna/add": "^3.3.1", + "@lerna/bootstrap": "^3.3.1", + "@lerna/changed": "^3.3.1", + "@lerna/clean": "^3.3.1", "@lerna/cli": "^3.2.0", - "@lerna/create": "^3.3.0", + "@lerna/create": "^3.3.1", "@lerna/diff": "^3.3.0", "@lerna/exec": "^3.3.0", - "@lerna/import": "^3.3.0", + "@lerna/import": "^3.3.1", "@lerna/init": "^3.3.0", "@lerna/link": "^3.3.0", "@lerna/list": "^3.3.0", - "@lerna/publish": "^3.3.0", + "@lerna/publish": "^3.3.1", "@lerna/run": "^3.3.0", - "@lerna/version": "^3.3.0", + "@lerna/version": "^3.3.1", "import-local": "^1.0.0", "npmlog": "^4.1.2" } @@ -4809,13 +4833,19 @@ }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, @@ -5283,6 +5313,14 @@ "dev": true, "requires": { "callsites": "^1.0.0" + }, + "dependencies": { + "callsites": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-1.0.1.tgz", + "integrity": "sha1-wUwkGIzo4dagMLTDyULmuolbaho=", + "dev": true + } } }, "parse-github-repo-url": { @@ -5358,7 +5396,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -5594,25 +5632,17 @@ } }, "razmin": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/razmin/-/razmin-0.4.0.tgz", - "integrity": "sha512-iP38gEa7v8qhckNEjsxb5Sckf12t0KAt0IQn9dISD5fjTcHlArLI/bfzcaOxM9CYZLbDyZ/CmbFg5CeUBZADWg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/razmin/-/razmin-0.5.0.tgz", + "integrity": "sha512-W40JFOuquSUpf+fxm+inYZfuV3qR+4iEc64saoOVAyLcjX6CGfSCGtjWBdN3BgJ2LTPzLsAVmMri1fUoJO9koA==", "dev": true, "requires": { "callsites": "^2.0.0", "colors": "^1.1.2", "require-glob": "^3.2.0", - "rxjs": "^5.5.6", + "rxjs": "^6.3.2", "source-map-support": "^0.5.0", "zone.js": "^0.8.20" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - } } }, "rc": { @@ -5774,7 +5804,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -6047,12 +6077,12 @@ } }, "rxjs": { - "version": "5.5.12", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", - "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz", + "integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==", "dev": true, "requires": { - "symbol-observable": "1.0.1" + "tslib": "^1.9.0" } }, "safe-buffer": { @@ -6498,7 +6528,7 @@ }, "stream-combiner": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", "dev": true, "requires": { @@ -6606,13 +6636,19 @@ }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, @@ -6641,12 +6677,6 @@ "integrity": "sha512-RSwmV55feF4vFo+DI+yBY1PayR/8amnL3S0G/d1tg8NjAzHVp13YjE2hay8QSGeAaHOdzTCsvZqAfib1iFSBqg==", "dev": true }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", - "dev": true - }, "tar": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", @@ -6722,14 +6752,14 @@ } }, "text-extensions": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.7.0.tgz", - "integrity": "sha512-AKXZeDq230UaSzaO5s3qQUZOaC7iKbzq0jOFL614R7d9R593HLqAOL0cYoqLdkNrjBSOdmoQI06yigq1TSBXAg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.8.0.tgz", + "integrity": "sha512-mVzjRxuWnDKs/qH1rbOJEVHLlSX9kty9lpi7lMvLgU9S74mQ8/Ozg9UPcKxShh0qG2NZ+NyPOPpcZU4C1Eld9A==", "dev": true }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -6859,6 +6889,12 @@ "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", "dev": true }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -7175,9 +7211,9 @@ "dev": true }, "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", "dev": true, "requires": { "lodash.sortby": "^4.7.0", @@ -7309,9 +7345,9 @@ } }, "wtfnode": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/wtfnode/-/wtfnode-0.7.0.tgz", - "integrity": "sha1-EgC22ZycqIGpiyTS2qxAJHOG2MY=", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/wtfnode/-/wtfnode-0.7.1.tgz", + "integrity": "sha512-WRcUxsOLw40NggdfhXw6sdcuTJTEaJZ97a0cPztepEUW0AaToyY/c4RGTOQkwj5mmZiuK5UBERZlUoK1n1lh/g==", "dev": true }, "xdg-basedir": { diff --git a/packages/web-server/package.json b/packages/web-server/package.json index ba473d11..e11ce4e6 100644 --- a/packages/web-server/package.json +++ b/packages/web-server/package.json @@ -45,7 +45,7 @@ "chai": "^3.5.0", "lerna": "^3.2.1", "nodemon": "^1.17.4", - "razmin": "^0.4.0", + "razmin": "^0.5.0", "require-glob": "^3.2.0", "rimraf": "^2.6.2", "supertest": "^3.1.0", diff --git a/packages/web-server/src/controller.ts b/packages/web-server/src/controller.ts index 62347299..5b8fce58 100644 --- a/packages/web-server/src/controller.ts +++ b/packages/web-server/src/controller.ts @@ -1,5 +1,5 @@ import { WebServer } from "./web-server"; -import { Provider, ReflectiveInjector, Injector } from "injection-js"; +import { Provider, ReflectiveInjector, Injector } from "@alterior/di"; import { RouteInstance } from './route'; import { ControllerAnnotation, ControllerOptions, MountOptions, RouteReflector } from "./metadata"; import * as express from 'express'; diff --git a/packages/web-server/src/metadata/route.ts b/packages/web-server/src/metadata/route.ts index 833199f4..66c03853 100644 --- a/packages/web-server/src/metadata/route.ts +++ b/packages/web-server/src/metadata/route.ts @@ -1,7 +1,7 @@ import * as express from 'express'; import { shallowClone } from '@alterior/common'; -import { Provider } from 'injection-js'; +import { Provider } from '@alterior/di'; export class RouteReflector { constructor(type : Function, mountPath? : string) { diff --git a/packages/web-server/src/middleware.ts b/packages/web-server/src/middleware.ts index 49e4f23c..4e019090 100644 --- a/packages/web-server/src/middleware.ts +++ b/packages/web-server/src/middleware.ts @@ -1,4 +1,4 @@ -import { Injector, ReflectiveInjector } from 'injection-js'; +import { Injector, ReflectiveInjector } from '@alterior/di'; import { Annotation } from '@alterior/annotations'; export class MiddlewareAnnotation extends Annotation {} diff --git a/packages/web-server/src/route.ts b/packages/web-server/src/route.ts index 46166b05..b1aee688 100644 --- a/packages/web-server/src/route.ts +++ b/packages/web-server/src/route.ts @@ -4,7 +4,7 @@ import * as express from 'express'; import { IAnnotation } from "@alterior/annotations"; import { InputAnnotation } from "./input"; import { RouteEvent, RouteDefinition } from "./metadata"; -import { ReflectiveInjector, Provider, Injector } from 'injection-js'; +import { ReflectiveInjector, Provider, Injector } from '@alterior/di'; import { prepareMiddleware } from "./middleware"; import { Annotations } from "@alterior/annotations"; import { WebServerSetupError, WebServer } from "./web-server"; diff --git a/packages/web-server/src/service.test.ts b/packages/web-server/src/service.test.ts index fb7f4302..b4fc989a 100644 --- a/packages/web-server/src/service.test.ts +++ b/packages/web-server/src/service.test.ts @@ -1,4 +1,4 @@ -import { suite } from "razmin"; +import { describe, it } from "razmin"; import { teststrap } from "./teststrap"; import { WebService, WebServiceAnnotation } from "./service"; import { Mount, Get } from "./metadata"; @@ -8,56 +8,54 @@ import { AppOptionsAnnotation } from "@alterior/runtime"; import { ModuleAnnotation } from "@alterior/di"; import { WebServerModule, WebServerOptionsRef } from "./web-server.module"; -suite(describe => { - describe("WebServiceDecorator", it => { - it('should work for a simple use case', async () => { - @WebService() - class TestService { - @Get('/foo') - getX() { - return Promise.resolve({ok: true}); - } +describe("WebServiceDecorator", () => { + it('should work for a simple use case', async () => { + @WebService() + class TestService { + @Get('/foo') + getX() { + return Promise.resolve({ok: true}); } - - await teststrap(TestService, async test => - await test.get('/foo').expect(200, {ok: true}) - ); - }); - - it('should attach the correct metadata', async () => { - @WebService({ - version: '1.2.3', - providers: [ { provide: 'foo', useValue: 123 } ], - server: { - port: 12321 - } - }) - class TestApp { + } + + await teststrap(TestService, async test => + await test.get('/foo').expect(200, {ok: true}) + ); + }); + + it('should attach the correct metadata', async () => { + @WebService({ + version: '1.2.3', + providers: [ { provide: 'foo', useValue: 123 } ], + server: { + port: 12321 } + }) + class TestApp { + } - let webServiceAnnotation = WebServiceAnnotation.getForClass(TestApp); - let appOptionsAnnotation = AppOptionsAnnotation.getForClass(TestApp); - let moduleAnnotation = ModuleAnnotation.getForClass(TestApp); + let webServiceAnnotation = WebServiceAnnotation.getForClass(TestApp); + let appOptionsAnnotation = AppOptionsAnnotation.getForClass(TestApp); + let moduleAnnotation = ModuleAnnotation.getForClass(TestApp); - expect(webServiceAnnotation).to.not.eq(null); - expect(appOptionsAnnotation).to.not.eq(null); - expect(moduleAnnotation).to.not.eq(null); + expect(webServiceAnnotation).to.not.eq(null); + expect(appOptionsAnnotation).to.not.eq(null); + expect(moduleAnnotation).to.not.eq(null); - expect(appOptionsAnnotation.options.version).to.eq('1.2.3'); - expect(moduleAnnotation.providers.length).to.eq(1); - expect(moduleAnnotation.providers[0]['provide']).to.eq('foo'); - expect(moduleAnnotation.providers[0]['useValue']).to.eq(123); + expect(appOptionsAnnotation.options.version).to.eq('1.2.3'); + expect(moduleAnnotation.providers.length).to.eq(1); + expect(moduleAnnotation.providers[0]['provide']).to.eq('foo'); + expect(moduleAnnotation.providers[0]['useValue']).to.eq(123); - let webServerModule = moduleAnnotation.imports.find(x => x['$module'] === WebServerModule); - expect(webServerModule).to.not.eq(null); - expect(webServerModule['providers'].length).to.be.gte(1); + let webServerModule = moduleAnnotation.imports.find(x => x['$module'] === WebServerModule); + expect(webServerModule).to.not.eq(null); + expect(webServerModule['providers'].length).to.be.gte(1); - let optionsProvider = webServerModule['providers'].find(x => x.provide == WebServerOptionsRef); - expect(optionsProvider).to.not.eq(null); + let optionsProvider = webServerModule['providers'].find(x => x.provide == WebServerOptionsRef); + expect(optionsProvider).to.not.eq(null); - let optionsRef : WebServerOptionsRef = optionsProvider.useValue; + let optionsRef : WebServerOptionsRef = optionsProvider.useValue; - expect(optionsRef.options.port).to.eq(12321); - }); + expect(optionsRef.options.port).to.eq(12321); }); }); \ No newline at end of file diff --git a/packages/web-server/src/web-server.ts b/packages/web-server/src/web-server.ts index 1cfa76d1..ef2b469e 100644 --- a/packages/web-server/src/web-server.ts +++ b/packages/web-server/src/web-server.ts @@ -1,6 +1,6 @@ import * as express from 'express'; -import { Injector, ReflectiveInjector } from "injection-js"; +import { Injector, ReflectiveInjector } from "@alterior/di"; import { prepareMiddleware } from "./middleware"; import { RouteEvent } from "./metadata"; import { BaseErrorT } from "@alterior/common";