Pārlūkot izejas kodu

Actually check types (#1918)

* types: stronger typings

* Remove remaining `declare module` things for uppy.use

* Format types standard-style-like

* Add `target` properties to plugin typings

* Add type for `replaceTargetContent` option

* xhr-upload: allow lowercase method

* opt in to stricter typechecks

* use the strictly typed version in all typings tests

* endtoend: use strict types in typescript test

* Add some comments to the typings file so IDEs will warn about untyped use()

* informer: remove obsolete option from typings

* react: generate prop types based on actual options types

* react: update types

* react: fix import in typings test

* companion-client: add `pluginId` property to ProviderOptions type

Co-Authored-By: Ifedapo .A. Olarewaju <ifedapoolarewaju@gmail.com>

* core: remove some `any` types

* *: add locale string types

* *: generate locale typings

* core: fix LocaleStrings<> type

* dashboard: add locale string type tests

* tus: inherit options typings from tus-js-client

* transloadit: add missing options types

* ci: do the required build steps before testing typings

* Support TokenStorage in types, add `title?: string` options

* form: update typings

* types: put TokenStorage type in dependents

* Document Uppy.StrictTypes

* if we pin at least we should use the latest

* Allow document.querySelector

I think it's a eslint-plugin-compat bug that it's marking querySelector
as not available in Android Chrome 78.

Co-authored-by: Ifedapo .A. Olarewaju <ifedapoolarewaju@gmail.com>
Renée Kooi 5 gadi atpakaļ
vecāks
revīzija
be66c47deb
83 mainītis faili ar 2135 papildinājumiem un 1166 dzēšanām
  1. 2 1
      .eslintrc.json
  2. 1 0
      .gitignore
  3. 2 1
      .travis.yml
  4. 24 0
      bin/locale-packs.js
  5. 869 87
      package-lock.json
  6. 64 63
      package.json
  7. 0 34
      packages/@uppy/aws-s3-multipart/types/aws-s3-multipart-tests.ts
  8. 32 22
      packages/@uppy/aws-s3-multipart/types/index.d.ts
  9. 39 0
      packages/@uppy/aws-s3-multipart/types/index.test-d.ts
  10. 0 11
      packages/@uppy/aws-s3/types/aws-s3-tests.ts
  11. 15 19
      packages/@uppy/aws-s3/types/index.d.ts
  12. 13 0
      packages/@uppy/aws-s3/types/index.test-d.ts
  13. 51 29
      packages/@uppy/companion-client/types/index.d.ts
  14. 2 0
      packages/@uppy/companion-client/types/index.test-d.ts
  15. 0 71
      packages/@uppy/core/types/core-tests.ts
  16. 203 113
      packages/@uppy/core/types/index.d.ts
  17. 91 0
      packages/@uppy/core/types/index.test-d.ts
  18. 0 32
      packages/@uppy/dashboard/types/dashboard-tests.ts
  19. 45 102
      packages/@uppy/dashboard/types/index.d.ts
  20. 64 0
      packages/@uppy/dashboard/types/index.test-d.ts
  21. 12 16
      packages/@uppy/drag-drop/types/index.d.ts
  22. 2 0
      packages/@uppy/drag-drop/types/index.test-d.ts
  23. 11 15
      packages/@uppy/dropbox/types/index.d.ts
  24. 2 0
      packages/@uppy/dropbox/types/index.test-d.ts
  25. 11 15
      packages/@uppy/facebook/types/index.d.ts
  26. 2 0
      packages/@uppy/facebook/types/index.test-d.ts
  27. 10 14
      packages/@uppy/file-input/types/index.d.ts
  28. 1 0
      packages/@uppy/file-input/types/index.test-d.ts
  29. 1 1
      packages/@uppy/form/src/index.js
  30. 11 16
      packages/@uppy/form/types/index.d.ts
  31. 1 0
      packages/@uppy/form/types/index.test-d.ts
  32. 8 14
      packages/@uppy/golden-retriever/types/index.d.ts
  33. 1 0
      packages/@uppy/golden-retriever/types/index.test-d.ts
  34. 11 15
      packages/@uppy/google-drive/types/index.d.ts
  35. 9 0
      packages/@uppy/google-drive/types/index.test-d.ts
  36. 5 19
      packages/@uppy/informer/types/index.d.ts
  37. 1 0
      packages/@uppy/informer/types/index.test-d.ts
  38. 11 15
      packages/@uppy/instagram/types/index.d.ts
  39. 2 0
      packages/@uppy/instagram/types/index.test-d.ts
  40. 11 15
      packages/@uppy/onedrive/types/index.d.ts
  41. 2 0
      packages/@uppy/onedrive/types/index.test-d.ts
  42. 7 13
      packages/@uppy/progress-bar/types/index.d.ts
  43. 2 0
      packages/@uppy/progress-bar/types/index.test-d.ts
  44. 11 2
      packages/@uppy/react/src/CommonTypes.d.ts
  45. 13 32
      packages/@uppy/react/src/Dashboard.d.ts
  46. 5 10
      packages/@uppy/react/src/DashboardModal.d.ts
  47. 5 7
      packages/@uppy/react/src/DragDrop.d.ts
  48. 5 8
      packages/@uppy/react/src/ProgressBar.d.ts
  49. 5 8
      packages/@uppy/react/src/StatusBar.d.ts
  50. 6 6
      packages/@uppy/react/types/index.d.ts
  51. 20 0
      packages/@uppy/react/types/index.test-d.tsx
  52. 6 13
      packages/@uppy/redux-dev-tools/types/index.d.ts
  53. 1 0
      packages/@uppy/redux-dev-tools/types/index.test-d.ts
  54. 12 14
      packages/@uppy/status-bar/types/index.d.ts
  55. 2 0
      packages/@uppy/status-bar/types/index.test-d.ts
  56. 7 7
      packages/@uppy/store-default/types/index.d.ts
  57. 6 0
      packages/@uppy/store-default/types/index.test-d.ts
  58. 0 6
      packages/@uppy/store-default/types/store-default-tests.ts
  59. 16 14
      packages/@uppy/store-redux/types/index.d.ts
  60. 13 0
      packages/@uppy/store-redux/types/index.test-d.ts
  61. 0 13
      packages/@uppy/store-redux/types/store-redux-tests.ts
  62. 8 12
      packages/@uppy/thumbnail-generator/types/index.d.ts
  63. 2 0
      packages/@uppy/thumbnail-generator/types/index.test-d.ts
  64. 30 27
      packages/@uppy/transloadit/types/index.d.ts
  65. 63 0
      packages/@uppy/transloadit/types/index.test-d.ts
  66. 0 53
      packages/@uppy/transloadit/types/transloadit-tests.ts
  67. 1 0
      packages/@uppy/tus/package.json
  68. 19 23
      packages/@uppy/tus/types/index.d.ts
  69. 1 0
      packages/@uppy/tus/types/index.test-d.ts
  70. 12 14
      packages/@uppy/url/types/index.d.ts
  71. 2 0
      packages/@uppy/url/types/index.test-d.ts
  72. 117 85
      packages/@uppy/utils/types/index.d.ts
  73. 1 0
      packages/@uppy/utils/types/index.test-d.ts
  74. 18 17
      packages/@uppy/webcam/types/index.d.ts
  75. 8 0
      packages/@uppy/webcam/types/index.test-d.ts
  76. 0 8
      packages/@uppy/webcam/types/webcam-tests.ts
  77. 15 21
      packages/@uppy/xhr-upload/types/index.d.ts
  78. 19 0
      packages/@uppy/xhr-upload/types/index.test-d.ts
  79. 0 9
      packages/@uppy/xhr-upload/types/xhr-upload-tests.ts
  80. 0 0
      packages/uppy/types/index.test-d.ts
  81. 1 1
      test/endtoend/typescript/main.ts
  82. 1 3
      tsconfig.json
  83. 33 0
      website/src/docs/uppy.md

+ 2 - 1
.eslintrc.json

@@ -33,7 +33,8 @@
     "polyfills": [
       "Promise",
       "fetch",
-      "Object.assign"
+      "Object.assign",
+      "document.querySelector"
     ]
   },
   "overrides": [{

+ 1 - 0
.gitignore

@@ -16,6 +16,7 @@ examples/dev/bundle.js
 test/endtoend/create-react-app/build/
 test/endtoend/create-react-app/coverage/
 uppy-*.tgz
+generatedLocale.d.ts
 
 website/db.json
 website/*.log

+ 2 - 1
.travis.yml

@@ -67,7 +67,8 @@ install:
 - npm ci
 script:
 - 'if [ -n "${LINT-}" ]; then npm run lint; fi'
-- 'if [ -n "${LINT-}" ]; then npm run test:type; fi'
+# Need to do a bunch of work to generate the locale typings 🙃
+- 'if [ -n "${LINT-}" ]; then npm run build:lib && npm run build:companion && npm run build:locale-pack && npm run test:type; fi'
 - 'if [ -n "${UNIT-}" ]; then npm run test:unit; fi'
 - 'if [ -n "${COMPANION_NODE6-}" ]; then nvm install 6.0.0 && nvm use 6.0.0; fi'
 - 'if [ -n "${COMPANION-}" ] || [ -n "${COMPANION_NODE6-}" ]; then npm run test:companion; fi'

+ 24 - 0
bin/locale-packs.js

@@ -154,6 +154,26 @@ function sortObjectAlphabetically (obj, sortFunc) {
   }, {})
 }
 
+function createTypeScriptLocale (plugin, pluginName) {
+  const allowedStringTypes = Object.keys(plugin.defaultLocale.strings)
+    .map(key => `  | '${key}'`)
+    .join('\n')
+
+  const pluginClassName = pluginName === 'core' ? 'Core' : plugin.id
+  const localePath = path.join(__dirname, '..', 'packages', '@uppy', pluginName, 'types', 'generatedLocale.d.ts')
+
+  const localeTypes =
+    'import Uppy = require(\'@uppy/core\')\n' +
+    '\n' +
+    `type ${pluginClassName}Locale = Uppy.Locale` + '<\n' +
+    allowedStringTypes + '\n' +
+    '>\n' +
+    '\n' +
+    `export = ${pluginClassName}Locale\n`
+
+  fs.writeFileSync(localePath, localeTypes)
+}
+
 function build () {
   const { plugins, sources } = buildPluginsList()
 
@@ -161,6 +181,10 @@ function build () {
     addLocaleToPack(plugins[pluginName], pluginName)
   }
 
+  for (const pluginName in plugins) {
+    createTypeScriptLocale(plugins[pluginName], pluginName)
+  }
+
   localePack = sortObjectAlphabetically(localePack)
 
   for (const pluginName in sources) {

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 869 - 87
package-lock.json


+ 64 - 63
package.json

@@ -69,69 +69,69 @@
     "uppy.io": "file:website"
   },
   "devDependencies": {
-    "@babel/cli": "7.7.5",
-    "@babel/core": "7.7.5",
-    "@babel/plugin-proposal-class-properties": "7.7.4",
-    "@babel/plugin-transform-object-assign": "7.7.4",
-    "@babel/plugin-transform-proto-to-assign": "7.7.4",
-    "@babel/plugin-transform-react-jsx": "7.7.4",
-    "@babel/polyfill": "7.4.4",
-    "@babel/preset-env": "7.7.6",
-    "@babel/register": "7.7.4",
+    "@babel/cli": "7.8.4",
+    "@babel/core": "7.8.4",
+    "@babel/plugin-proposal-class-properties": "7.8.3",
+    "@babel/plugin-transform-object-assign": "7.8.3",
+    "@babel/plugin-transform-proto-to-assign": "7.8.3",
+    "@babel/plugin-transform-react-jsx": "7.8.3",
+    "@babel/polyfill": "7.8.3",
+    "@babel/preset-env": "7.8.4",
+    "@babel/register": "7.8.3",
     "@jamen/lorem": "0.2.0",
     "@lerna/run": "3.20.0",
-    "@octokit/rest": "16.25.0",
-    "@size-limit/preset-big-lib": "2.2.2",
-    "@types/aws-serverless-express": "3.0.1",
+    "@octokit/rest": "16.43.1",
+    "@size-limit/preset-big-lib": "2.2.4",
+    "@types/aws-serverless-express": "3.3.3",
     "@types/compression": "0.0.36",
     "@types/connect-redis": "0.0.7",
-    "@types/cookie-parser": "1.4.1",
-    "@types/cors": "2.8.3",
-    "@types/express-session": "1.15.6",
+    "@types/cookie-parser": "1.4.2",
+    "@types/cors": "2.8.6",
+    "@types/express-session": "1.15.16",
     "@types/helmet": "0.0.37",
-    "@types/jsonwebtoken": "7.2.5",
-    "@types/lodash.merge": "4.6.3",
-    "@types/morgan": "1.7.35",
-    "@types/ms": "0.7.30",
-    "@types/node": "12.12.17",
-    "@types/react": "16.9.16",
-    "@types/request": "2.0.9",
+    "@types/jsonwebtoken": "7.2.8",
+    "@types/lodash.merge": "4.6.6",
+    "@types/morgan": "1.7.37",
+    "@types/ms": "0.7.31",
+    "@types/node": "12.12.27",
+    "@types/react": "16.9.19",
+    "@types/request": "2.48.4",
     "@types/tus-js-client": "1.8.0",
-    "@types/uuid": "3.4.3",
+    "@types/uuid": "3.4.7",
     "@types/ws": "3.2.1",
-    "@wdio/cli": "5.16.15",
-    "@wdio/local-runner": "5.16.15",
-    "@wdio/mocha-framework": "5.16.15",
+    "@wdio/cli": "5.18.6",
+    "@wdio/local-runner": "5.18.6",
+    "@wdio/mocha-framework": "5.18.6",
     "@wdio/sauce-service": "5.16.10",
     "adm-zip": "0.4.14",
     "aliasify": "2.1.0",
-    "autoprefixer": "9.7.3",
+    "autoprefixer": "9.7.4",
     "babel-eslint": "10.0.3",
     "babel-jest": "24.9.0",
     "babel-plugin-inline-package-json": "2.0.0",
     "babelify": "10.0.0",
     "brake": "1.0.1",
     "browser-resolve": "1.11.3",
-    "browser-sync": "2.26.5",
-    "browserify": "16.2.3",
+    "browser-sync": "2.26.7",
+    "browserify": "16.5.0",
     "chai": "4.2.0",
     "chalk": "2.4.2",
     "cssnano": "4.1.10",
     "disc": "1.3.3",
     "envify": "4.1.0",
-    "enzyme": "3.9.0",
-    "enzyme-adapter-react-16": "1.11.2",
-    "es6-promise": "4.2.5",
-    "eslint": "6.7.2",
+    "enzyme": "3.11.0",
+    "enzyme-adapter-react-16": "1.15.2",
+    "es6-promise": "4.2.8",
+    "eslint": "6.8.0",
     "eslint-config-standard": "14.1.0",
     "eslint-config-standard-jsx": "8.1.0",
-    "eslint-plugin-compat": "3.3.0",
-    "eslint-plugin-import": "2.19.1",
-    "eslint-plugin-jest": "22.17.0",
-    "eslint-plugin-jsdoc": "15.5.3",
+    "eslint-plugin-compat": "3.5.1",
+    "eslint-plugin-import": "2.20.1",
+    "eslint-plugin-jest": "22.21.0",
+    "eslint-plugin-jsdoc": "15.12.2",
     "eslint-plugin-node": "10.0.0",
     "eslint-plugin-promise": "4.2.1",
-    "eslint-plugin-react": "7.17.0",
+    "eslint-plugin-react": "7.18.3",
     "eslint-plugin-standard": "4.0.1",
     "events.once": "2.0.2",
     "exorcist": "1.0.1",
@@ -139,51 +139,52 @@
     "fakefile": "0.0.9",
     "flat": "4.1.0",
     "github-contributors-list": "1.2.4",
-    "glob": "7.1.3",
+    "glob": "7.1.6",
     "globby": "9.2.0",
-    "gzip-size": "5.0.0",
+    "gzip-size": "5.1.1",
     "http-proxy": "1.18.0",
     "isomorphic-fetch": "2.2.1",
     "jest": "24.9.0",
-    "json3": "3.3.2",
+    "json3": "3.3.3",
     "last-commit-message": "1.0.0",
-    "lerna": "3.17.0",
+    "lerna": "3.20.2",
     "lint-staged": "9.5.0",
-    "mime-types": "2.1.24",
-    "minify-stream": "1.2.0",
+    "mime-types": "2.1.26",
+    "minify-stream": "1.2.1",
     "mkdirp": "0.5.1",
     "multi-glob": "1.0.2",
     "namespace-emitter": "2.0.1",
     "nock": "9.6.1",
     "node-sass": "4.13.1",
-    "nodemon": "1.17.5",
+    "nodemon": "1.19.4",
     "npm-auth-to-token": "1.0.0",
-    "npm-packlist": "1.4.7",
+    "npm-packlist": "1.4.8",
     "npm-run-all": "4.1.5",
     "onchange": "4.1.0",
-    "pacote": "9.5.11",
-    "postcss": "7.0.24",
-    "postcss-safe-important": "1.1.0",
+    "pacote": "9.5.12",
+    "postcss": "7.0.26",
+    "postcss-safe-important": "1.2.0",
     "pre-commit": "1.2.2",
-    "pretty-bytes": "5.1.0",
-    "react": "16.8.6",
-    "react-dom": "16.8.6",
-    "redux": "4.0.1",
-    "replace-x": "1.5.0",
-    "rimraf": "2.6.3",
+    "pretty-bytes": "5.3.0",
+    "react": "16.12.0",
+    "react-dom": "16.12.0",
+    "redux": "4.0.5",
+    "replace-x": "1.7.2",
+    "rimraf": "2.7.1",
     "stringify-object": "3.3.0",
-    "supertest": "3.0.0",
-    "tar": "4.4.8",
+    "supertest": "3.4.2",
+    "tar": "4.4.13",
     "temp-write": "3.4.0",
-    "tinyify": "2.5.0",
+    "tinyify": "2.5.2",
     "touch": "3.1.0",
+    "tsd": "0.10.0",
     "tsify": "4.0.1",
     "tus-node-server": "0.3.2",
-    "typescript": "3.7.3",
-    "verdaccio": "4.2.1",
+    "typescript": "3.7.5",
+    "verdaccio": "4.4.2",
     "watchify": "3.11.1",
-    "webdriverio": "5.16.15",
-    "webpack": "4.41.1",
+    "webdriverio": "5.18.6",
+    "webpack": "4.41.6",
     "whatwg-fetch": "3.0.0"
   },
   "scripts": {
@@ -219,7 +220,7 @@
     "test:endtoend:registry": "verdaccio --listen 4002 --config test/endtoend/verdaccio.yaml",
     "test:endtoend": "npm run test:endtoend:prepare-ci && wdio test/endtoend/wdio.remote.conf.js",
     "test:locale-packs": "node ./bin/locale-packs.js test",
-    "test:type": "tsc -p .",
+    "test:type": "lerna exec --scope '@uppy/*' --ignore '@uppy/{react-native,locales,companion,provider-views,robodog}' tsd",
     "test:unit": "npm run build:lib && jest",
     "test:watch": "jest --watch",
     "test:size": "size-limit",

+ 0 - 34
packages/@uppy/aws-s3-multipart/types/aws-s3-multipart-tests.ts

@@ -1,34 +0,0 @@
-import Uppy = require('@uppy/core');
-import AwsS3Multipart = require('../');
-
-{
-  const uppy = Uppy();
-  uppy.use(AwsS3Multipart, {
-    createMultipartUpload(file) {
-      file // $ExpectType Uppy.UppyFile
-    },
-    listParts(file, opts) {
-      file // $ExpectType Uppy.UppyFile
-      opts.uploadId // $ExpectType string
-      opts.key // $ExpectType string
-    },
-    prepareUploadPart(file, part) {
-      file // $ExpectType Uppy.UppyFile
-      part.uploadId // $ExpectType string
-      part.key // $ExpectType string
-      part.body // $ExpectType Blob
-      part.number // $ExpectType number
-    },
-    abortMultipartUpload(file, opts) {
-      file // $ExpectType Uppy.UppyFile
-      opts.uploadId // $ExpectType string
-      opts.key // $ExpectType string
-    },
-    completeMultipartUpload(file, opts) {
-      file // $ExpectType Uppy.UppyFile
-      opts.uploadId // $ExpectType string
-      opts.key // $ExpectType string
-      opts.parts[0] // $ExpectType AwsS3Multipart.AwsS3Part
-    },
-  });
-}

+ 32 - 22
packages/@uppy/aws-s3-multipart/types/index.d.ts

@@ -1,32 +1,42 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+
+type MaybePromise<T> = T | Promise<T>
 
 declare module AwsS3Multipart {
   interface AwsS3Part {
-    PartNumber?: number;
-    Size?: number;
-    ETag?: string;
+    PartNumber?: number
+    Size?: number
+    ETag?: string
   }
 
   interface AwsS3MultipartOptions extends Uppy.PluginOptions {
-    companionUrl: string;
-    createMultipartUpload(file: Uppy.UppyFile): Promise<{ uploadId: string, key: string }>;
-    listParts(file: Uppy.UppyFile, opts: { uploadId: string, key: string }): Promise<AwsS3Part[]>;
-    prepareUploadPart(file: Uppy.UppyFile, partData: { uploadId: string, key: string, body: Blob, number: number }): Promise<{ url: string, headers?: {[k: string]: string}}>;
-    abortMultipartUpload(file: Uppy.UppyFile, opts: { uploadId: string, key: string }): Promise<void>;
-    completeMultipartUpload(file: Uppy.UppyFile, opts: { uploadId: string, key: string, parts: AwsS3Part[] }): Promise<{ location?: string }>;
-    timeout: number;
-    limit: number;
+    companionUrl?: string
+    createMultipartUpload?: (
+      file: Uppy.UppyFile
+    ) => MaybePromise<{ uploadId: string; key: string }>
+    listParts?: (
+      file: Uppy.UppyFile,
+      opts: { uploadId: string; key: string }
+    ) => MaybePromise<AwsS3Part[]>
+    prepareUploadPart?: (
+      file: Uppy.UppyFile,
+      partData: { uploadId: string; key: string; body: Blob; number: number }
+    ) => MaybePromise<{ url: string, headers?: { [k: string]: string } }>
+    abortMultipartUpload?: (
+      file: Uppy.UppyFile,
+      opts: { uploadId: string; key: string }
+    ) => MaybePromise<void>
+    completeMultipartUpload?: (
+      file: Uppy.UppyFile,
+      opts: { uploadId: string; key: string; parts: AwsS3Part[] }
+    ) => MaybePromise<{ location?: string }>
+    timeout?: number
+    limit?: number
   }
 }
 
-declare class AwsS3Multipart extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<AwsS3Multipart.AwsS3MultipartOptions>);
-}
-
-export = AwsS3Multipart;
+declare class AwsS3Multipart extends Uppy.Plugin<
+  AwsS3Multipart.AwsS3MultipartOptions
+> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof AwsS3Multipart, opts: Partial<AwsS3Multipart.AwsS3MultipartOptions>): Uppy.Uppy;
-  }
-}
+export = AwsS3Multipart

+ 39 - 0
packages/@uppy/aws-s3-multipart/types/index.test-d.ts

@@ -0,0 +1,39 @@
+import { expectType } from 'tsd'
+import Uppy = require('@uppy/core')
+import AwsS3Multipart = require('../')
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  uppy.use(AwsS3Multipart, {
+    createMultipartUpload (file) {
+      expectType<Uppy.UppyFile>(file)
+      return { uploadId: '', key: '' }
+    },
+    listParts (file, opts) {
+      expectType<Uppy.UppyFile>(file)
+      expectType<string>(opts.uploadId)
+      expectType<string>(opts.key)
+      return []
+    },
+    prepareUploadPart (file, part) {
+      expectType<Uppy.UppyFile>(file)
+      expectType<string>(part.uploadId)
+      expectType<string>(part.key)
+      expectType<Blob>(part.body)
+      expectType<number>(part.number)
+      return { url: '' }
+    },
+    abortMultipartUpload (file, opts) {
+      expectType<Uppy.UppyFile>(file)
+      expectType<string>(opts.uploadId)
+      expectType<string>(opts.key)
+    },
+    completeMultipartUpload (file, opts) {
+      expectType<Uppy.UppyFile>(file)
+      expectType<string>(opts.uploadId)
+      expectType<string>(opts.key)
+      expectType<AwsS3Multipart.AwsS3Part>(opts.parts[0])
+      return {}
+    }
+  })
+}

+ 0 - 11
packages/@uppy/aws-s3/types/aws-s3-tests.ts

@@ -1,11 +0,0 @@
-import Uppy = require('@uppy/core');
-import AwsS3 = require('../');
-
-{
-  const uppy = Uppy();
-  uppy.use(AwsS3, {
-    getUploadParameters(file) {
-      file // $ExpectType Uppy.UppyFile
-    }
-  });
-}

+ 15 - 19
packages/@uppy/aws-s3/types/index.d.ts

@@ -1,29 +1,25 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+
+type MaybePromise<T> = T | Promise<T>
 
 declare module AwsS3 {
   interface AwsS3UploadParameters {
-    method?: string;
-    url: string;
-    fields?: { [type: string]: string };
-    headers?: { [type: string]: string };
+    method?: string
+    url: string
+    fields?: { [type: string]: string }
+    headers?: { [type: string]: string }
   }
 
   interface AwsS3Options extends Uppy.PluginOptions {
-    companionUrl: string;
-    getUploadParameters(file: Uppy.UppyFile): Promise<AwsS3UploadParameters>;
-    timeout: number;
-    limit: number;
+    companionUrl?: string
+    getUploadParameters?: (
+      file: Uppy.UppyFile
+    ) => MaybePromise<AwsS3UploadParameters>
+    timeout?: number
+    limit?: number
   }
 }
 
-declare class AwsS3 extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<AwsS3.AwsS3Options>);
-}
-
-export = AwsS3;
+declare class AwsS3 extends Uppy.Plugin<AwsS3.AwsS3Options> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof AwsS3, opts: Partial<AwsS3.AwsS3Options>): Uppy.Uppy;
-  }
-}
+export = AwsS3

+ 13 - 0
packages/@uppy/aws-s3/types/index.test-d.ts

@@ -0,0 +1,13 @@
+import { expectType } from 'tsd'
+import Uppy = require('@uppy/core')
+import AwsS3 = require('../')
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  uppy.use(AwsS3, {
+    getUploadParameters (file) {
+      expectType<Uppy.UppyFile>(file)
+      return { url: '' }
+    }
+  })
+}

+ 51 - 29
packages/@uppy/companion-client/types/index.d.ts

@@ -1,48 +1,70 @@
+/**
+ * Async storage interface, similar to `localStorage`. This can be used to
+ * implement custom storages for authentication tokens.
+ */
+export interface TokenStorage {
+  setItem: (key: string, value: string) => Promise<void>
+  getItem: (key: string) => Promise<string>
+  removeItem: (key: string) => Promise<void>
+}
+
 export interface RequestClientOptions {
-  companionUrl: string;
-  companionHeaders?: object;
+  companionUrl: string
+  companionHeaders?: object
   /**
    * Deprecated, use `companionHeaders` instead.
    */
-  serverHeaders?: object;
+  serverHeaders?: object
 }
 
 export class RequestClient {
-  constructor (uppy: any, opts: RequestClientOptions);
-  get (path: string): Promise<any>;
-  post (path: string, data: object): Promise<any>;
-  delete (path: string, data: object): Promise<any>;
+  constructor (uppy: any, opts: RequestClientOptions)
+  get (path: string): Promise<any>
+  post (path: string, data: object): Promise<any>
+  delete (path: string, data: object): Promise<any>
 }
 
-export interface ProviderOptions extends RequestClientOptions {
-  provider: string;
-  authProvider?: string;
-  name?: string;
+/**
+ * Options for Providers that can be passed in by Uppy users through
+ * Plugin constructors.
+ */
+export interface PublicProviderOptions extends RequestClientOptions {
+  companionAllowedHosts?: string | RegExp | Array<string | RegExp>
+}
+
+/**
+ * Options for Providers, including internal options that Plugins can set.
+ */
+export interface ProviderOptions extends PublicProviderOptions {
+  provider: string
+  authProvider?: string
+  name?: string
+  pluginId: string
 }
 
 export class Provider extends RequestClient {
-  constructor (uppy: any, opts: ProviderOptions);
-  checkAuth (): Promise<boolean>;
-  authUrl (): string;
-  fileUrl (id: string): string;
-  list (directory: string): Promise<any>;
-  logout (redirect?: string): Promise<any>;
-  static initPlugin(plugin: any, opts: object, defaultOpts?: object): void;
+  constructor (uppy: any, opts: ProviderOptions)
+  checkAuth (): Promise<boolean>
+  authUrl (): string
+  fileUrl (id: string): string
+  list (directory: string): Promise<any>
+  logout (redirect?: string): Promise<any>
+  static initPlugin (plugin: any, opts: object, defaultOpts?: object): void
 }
 
 export interface SocketOptions {
-  target: string;
-  autoOpen?: boolean;
+  target: string
+  autoOpen?: boolean
 }
 
 export class Socket {
-  isOpen: boolean;
-
-  constructor (opts: SocketOptions);
-  open (): void;
-  close (): void;
-  send (action: string, payload: any): void;
-  on (action: string, handler: (param: any) => void): void;
-  once (action: string, handler: (param: any) => void): void;
-  emit (action: string, payload: (param: any) => void): void;
+  isOpen: boolean
+
+  constructor (opts: SocketOptions)
+  open (): void
+  close (): void
+  send (action: string, payload: any): void
+  on (action: string, handler: (param: any) => void): void
+  once (action: string, handler: (param: any) => void): void
+  emit (action: string, payload: (param: any) => void): void
 }

+ 2 - 0
packages/@uppy/companion-client/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import { RequestClient, Provider, Socket } from '../'
+// TODO tests

+ 0 - 71
packages/@uppy/core/types/core-tests.ts

@@ -1,71 +0,0 @@
-import Uppy = require('../')
-import DefaultStore = require('@uppy/store-default')
-
-{
-  const uppy = Uppy()
-  uppy.addFile({
-    data: new Blob([new ArrayBuffer(1024)], { type: 'application/octet-stream' })
-  })
-
-  uppy.upload().then((result) => {
-    result.successful[0] // $ExpectType UppyFile
-    result.failed[0] // $ExpectType UppyFile
-  })
-}
-
-{
-  const store = DefaultStore()
-  const uppy = Uppy({ store })
-}
-
-{
-  const uppy = Uppy()
-  // this doesn't exist but type checking works anyway :)
-  const f = uppy.getFile('virtual')
-  if (f && f.progress && f.progress.uploadStarted === null) {
-    f.progress.uploadStarted = Date.now()
-  }
-
-  if (f && f.response && f.response.status === 200) {
-    f.response.body // $ExpectType any
-  }
-  // f.response!.status === '200' // $ExpectError
-}
-
-{
-  type Meta = {};
-  type ResponseBody = {
-    averageColor: string
-  }
-  const uppy = Uppy()
-  const f = uppy.getFile<Meta, ResponseBody>('virtual')!
-  f.response!.body // $ExpectType ResponseBody
-}
-
-{
-  const uppy = Uppy()
-  uppy.addFile({
-    name: 'empty.json',
-    data: new Blob(['null'], { type: 'application/json' }),
-    meta: { path: 'path/to/file' }
-  })
-}
-
-{
-  const uppy = Uppy()
-  // can emit events with internal event types
-  uppy.emit('upload')
-  uppy.emit('complete', () => {})
-  uppy.emit('error', () => {})
-
-  // can emit events with custom event types
-  uppy.emit('dashboard:modal-closed', () => {})
-
-  // can register listners for internal events
-  uppy.on('upload', () => {})
-  uppy.on('complete', () => {})
-  uppy.on('error', () => {})
-
-  // can register listners on custom events
-  uppy.on('dashboard:modal-closed', () => {})
-}

+ 203 - 113
packages/@uppy/core/types/index.d.ts

@@ -1,104 +1,139 @@
-import UppyUtils = require('@uppy/utils');
+import UppyUtils = require('@uppy/utils')
 
 declare module Uppy {
   // Utility types
-  type OmitKey<T, Key> = Pick<T, Exclude<keyof T, Key>>;
+  type OmitKey<T, Key> = Pick<T, Exclude<keyof T, Key>>
 
   // These are defined in @uppy/utils instead of core so it can be used there without creating import cycles
-  export type UppyFile<TMeta extends IndexedObject<any> = {}, TBody extends IndexedObject<any> = {}> = UppyUtils.UppyFile<TMeta, TBody>;
-  export type Store = UppyUtils.Store;
-  export type InternalMetadata = UppyUtils.InternalMetadata;
+  export type UppyFile<
+    TMeta extends IndexedObject<any> = {},
+    TBody extends IndexedObject<any> = {}
+  > = UppyUtils.UppyFile<TMeta, TBody>
+  export type Store = UppyUtils.Store
+  export type InternalMetadata = UppyUtils.InternalMetadata
 
   interface IndexedObject<T> {
-    [key: string]: T;
-    [key: number]: T;
+    [key: string]: T
+    [key: number]: T
   }
 
   interface UploadedUppyFile<TMeta, TBody> extends UppyFile<TMeta, TBody> {
-    uploadURL: string;
+    uploadURL: string
   }
 
   interface FailedUppyFile<TMeta, TBody> extends UppyFile<TMeta, TBody> {
-    error: string;
+    error: string
   }
 
   // Replace the `meta` property type with one that allows omitting internal metadata; addFile() will add that
-  type UppyFileWithoutMeta<TMeta, TBody> = OmitKey<UppyFile<TMeta, TBody>, 'meta'>;
-  interface AddFileOptions<TMeta = IndexedObject<any>, TBody = IndexedObject<any>> extends Partial<UppyFileWithoutMeta<TMeta, TBody>> {
+  type UppyFileWithoutMeta<TMeta, TBody> = OmitKey<
+    UppyFile<TMeta, TBody>,
+    'meta'
+  >
+  interface AddFileOptions<
+    TMeta = IndexedObject<any>,
+    TBody = IndexedObject<any>
+  > extends Partial<UppyFileWithoutMeta<TMeta, TBody>> {
     // `.data` is the only required property here.
-    data: Blob | File;
-    meta?: Partial<InternalMetadata> & TMeta;
+    data: Blob | File
+    meta?: Partial<InternalMetadata> & TMeta
   }
 
   interface PluginOptions {
-    id?: string;
+    id?: string
+  }
+  interface DefaultPluginOptions extends PluginOptions {
+    [prop: string]: any
   }
 
-  class Plugin {
-    id: string;
-    uppy: Uppy;
-    type: string;
-    constructor(uppy: Uppy, opts?: PluginOptions);
-    getPluginState(): object;
-    setPluginState(update: any): object;
-    update(state?: object): void;
-    mount(target: any, plugin: any): void;
-    render(state: object): void;
-    addTarget(plugin: any): void;
-    unmount(): void;
-    install(): void;
-    uninstall(): void;
+  type PluginTarget = string | Element | typeof Plugin
+
+  class Plugin<TOptions extends PluginOptions = DefaultPluginOptions> {
+    id: string
+    uppy: Uppy
+    type: string
+    constructor(uppy: Uppy, opts?: TOptions)
+    getPluginState(): object
+    setPluginState(update: IndexedObject<any>): object
+    update(state?: object): void
+    mount(target: PluginTarget, plugin: typeof Plugin): void
+    render(state: object): void
+    addTarget<TPlugin extends Plugin>(plugin: TPlugin): void
+    unmount(): void
+    install(): void
+    uninstall(): void
   }
 
-  interface LocaleStrings {
-    [key: string]: string | LocaleStrings;
+  type LocaleStrings<TNames extends string> = {
+    [K in TNames]?: string | { [n: number]: string }
   }
-  interface Locale {
-    strings: LocaleStrings;
-    pluralize?: (n: number) => number;
+  interface Locale<TNames extends string = string> {
+    strings: LocaleStrings<TNames>
+    pluralize?: (n: number) => number
   }
 
   interface Restrictions {
-    maxFileSize: number | null;
-    maxNumberOfFiles: number | null;
-    minNumberOfFiles: number | null;
-    allowedFileTypes: string[] | null;
+    maxFileSize?: number | null
+    maxNumberOfFiles?: number | null
+    minNumberOfFiles?: number | null
+    allowedFileTypes?: string[] | null
   }
 
-  interface UppyOptions {
-    id: string;
-    autoProceed: boolean;
-    allowMultipleUploads: boolean;
-    debug: boolean;
-    restrictions: Partial<Restrictions>;
-    target: string | Plugin;
-    meta: any;
-    onBeforeFileAdded: (currentFile: UppyFile, files: {[key: string]: UppyFile}) => UppyFile | boolean | undefined;
-    onBeforeUpload: (files: {[key: string]: UppyFile}) => {[key: string]: UppyFile} | boolean;
-    locale: Locale;
-    store: Store;
+  interface UppyOptions<TMeta extends IndexedObject<any> = {}> {
+    id?: string
+    autoProceed?: boolean
+    allowMultipleUploads?: boolean
+    debug?: boolean
+    restrictions?: Restrictions
+    meta?: TMeta
+    onBeforeFileAdded?: (
+      currentFile: UppyFile<TMeta>,
+      files: { [key: string]: UppyFile<TMeta> }
+    ) => UppyFile<TMeta> | boolean | undefined
+    onBeforeUpload?: (files: {
+      [key: string]: UppyFile<TMeta>
+    }) => { [key: string]: UppyFile<TMeta> } | boolean
+    locale?: Locale
+    store?: Store
   }
 
-  interface UploadResult<TMeta extends IndexedObject<any> = {}, TBody extends IndexedObject<any> = {}> {
-    successful: UploadedUppyFile<TMeta, TBody>[];
-    failed: FailedUppyFile<TMeta, TBody>[];
+  interface UploadResult<
+    TMeta extends IndexedObject<any> = {},
+    TBody extends IndexedObject<any> = {}
+  > {
+    successful: UploadedUppyFile<TMeta, TBody>[]
+    failed: FailedUppyFile<TMeta, TBody>[]
   }
 
-  interface State<TMeta extends IndexedObject<any> = {}, TBody extends IndexedObject<any> = {}> extends IndexedObject<any> {
-    capabilities?: {resumableUploads?: boolean};
-    currentUploads: {};
-    error?: string;
-    files: {[key: string]: UploadedUppyFile<TMeta, TBody> | FailedUppyFile<TMeta, TBody>};
+  interface State<
+    TMeta extends IndexedObject<any> = {},
+    TBody extends IndexedObject<any> = {}
+  > extends IndexedObject<any> {
+    capabilities?: { resumableUploads?: boolean }
+    currentUploads: {}
+    error?: string
+    files: {
+      [key: string]:
+        | UploadedUppyFile<TMeta, TBody>
+        | FailedUppyFile<TMeta, TBody>
+    }
     info?: {
-      isHidden: boolean;
-      type: string;
-      message: string;
-      details: string;
-    };
-    plugins?: IndexedObject<any>;
-    totalProgress: number;
+      isHidden: boolean
+      type: string
+      message: string
+      details: string
+    }
+    plugins?: IndexedObject<any>
+    totalProgress: number
   }
-  type LogLevel = 'info' | 'warning' | 'error';
+
+  type LogLevel = 'info' | 'warning' | 'error'
+
+  /** Enable the old, untyped `uppy.use()` signature. */
+  type LooseTypes = 'loose'
+  /** Disable the old, untyped `uppy.use()` signature. */
+  type StrictTypes = 'strict'
+  type TypeChecking = LooseTypes | StrictTypes
 
   // This hack accepts _any_ string for `Event`, but also tricks VSCode and friends into providing autocompletions
   // for the names listed. https://github.com/microsoft/TypeScript/issues/29729#issuecomment-505826972
@@ -106,57 +141,112 @@ declare module Uppy {
   type Event = LiteralUnion<'file-added' | 'file-removed' | 'upload' | 'upload-progress' | 'upload-success' | 'complete' | 'error' | 'upload-error' |
                'upload-retry' | 'info-visible' | 'info-hidden' | 'cancel-all' | 'restriction-failed' | 'reset-progress'>;
 
-  class Uppy {
-    constructor(opts?: Partial<UppyOptions>);
-    on<TMeta extends IndexedObject<any> = {}>(event: 'upload-success', callback: (file: UppyFile<TMeta>, body: any, uploadURL: string) => void): Uppy;
-    on<TMeta extends IndexedObject<any> = {}>(event: 'complete', callback: (result: UploadResult<TMeta>) => void): Uppy;
-    on(event: Event, callback: (...args: any[]) => void): Uppy;
-    off(event: Event, callback: any): Uppy;
+  type UploadHandler = (fileIDs: string[]) => Promise<void>
+
+  class Uppy<TUseStrictTypes extends TypeChecking = TypeChecking> {
+    constructor(opts?: UppyOptions)
+    on<TMeta extends IndexedObject<any> = {}>(
+      event: 'upload-success',
+      callback: (file: UppyFile<TMeta>, body: any, uploadURL: string) => void
+    ): this
+    on<TMeta extends IndexedObject<any> = {}>(
+      event: 'complete',
+      callback: (result: UploadResult<TMeta>) => void
+    ): this
+    on(event: Event, callback: (...args: any[]) => void): this
+    off(event: Event, callback: (...args: any[]) => void): this
     /**
      * For use by plugins only!
      */
-    emit(event: Event, ...args: any[]): void;
-    updateAll(state: object): void;
-    setState(patch: object): void;
-    getState<TMeta extends IndexedObject<any> = {}>(): State<TMeta>;
-    readonly state: State;
-    setFileState(fileID: string, state: object): void;
-    resetProgress(): void;
-    addPreProcessor(fn: any): void;
-    removePreProcessor(fn: any): void;
-    addPostProcessor(fn: any): void;
-    removePostProcessor(fn: any): void;
-    addUploader(fn: any): void;
-    removeUploader(fn: any): void;
-    setMeta<TMeta extends IndexedObject<any> = {}>(data: TMeta): void;
-    setFileMeta<TMeta extends IndexedObject<any> = {}>(fileID: string, data: TMeta): void;
-    getFile<TMeta extends IndexedObject<any> = {}, TBody extends IndexedObject<any> = {}>(fileID: string): UppyFile<TMeta, TBody>;
-    getFiles<TMeta extends IndexedObject<any> = {}, TBody extends IndexedObject<any> = {}>(): Array<UppyFile<TMeta, TBody>>;
-    addFile<TMeta extends IndexedObject<any> = {}>(file: AddFileOptions<TMeta>): void;
-    removeFile(fileID: string): void;
-    pauseResume(fileID: string): boolean;
-    pauseAll(): void;
-    resumeAll(): void;
-    retryAll(): void;
-    cancelAll(): void;
-    retryUpload(fileID: string): any;
-    reset(): void;
-    getID(): string;
-    use<T extends typeof Plugin>(pluginClass: T, opts: object): Uppy;
-    getPlugin(name: string): Plugin;
-    iteratePlugins(callback: (plugin: Plugin) => void): void;
-    removePlugin(instance: Plugin): void;
-    close(): void;
-    info(message: string | {message: string; details: string}, type?: LogLevel, duration?: number): void;
-    hideInfo(): void;
-    log(msg: string, type?: LogLevel): void;
-    run(): Uppy;
-    restore<TMeta extends IndexedObject<any> = {}>(uploadID: string): Promise<UploadResult>;
-    addResultData(uploadID: string, data: object): void;
-    upload<TMeta extends IndexedObject<any> = {}>(): Promise<UploadResult>;
+    emit(event: Event, ...args: any[]): void
+    updateAll(state: object): void
+    setState(patch: object): void
+    getState<TMeta extends IndexedObject<any> = {}>(): State<TMeta>
+    readonly state: State
+    setFileState(fileID: string, state: object): void
+    resetProgress(): void
+    addPreProcessor(fn: UploadHandler): void
+    removePreProcessor(fn: UploadHandler): void
+    addPostProcessor(fn: UploadHandler): void
+    removePostProcessor(fn: UploadHandler): void
+    addUploader(fn: UploadHandler): void
+    removeUploader(fn: UploadHandler): void
+    setMeta<TMeta extends IndexedObject<any> = {}>(data: TMeta): void
+    setFileMeta<TMeta extends IndexedObject<any> = {}>(
+      fileID: string,
+      data: TMeta
+    ): void
+    getFile<
+      TMeta extends IndexedObject<any> = {},
+      TBody extends IndexedObject<any> = {}
+    >(fileID: string): UppyFile<TMeta, TBody>
+    getFiles<
+      TMeta extends IndexedObject<any> = {},
+      TBody extends IndexedObject<any> = {}
+    >(): Array<UppyFile<TMeta, TBody>>
+    addFile<TMeta extends IndexedObject<any> = {}>(
+      file: AddFileOptions<TMeta>
+    ): void
+    removeFile(fileID: string): void
+    pauseResume(fileID: string): boolean
+    pauseAll(): void
+    resumeAll(): void
+    retryAll<TMeta extends IndexedObject<any> = {}>(): Promise<UploadResult<TMeta>>
+    cancelAll(): void
+    retryUpload<TMeta extends IndexedObject<any> = {}>(fileID: string): Promise<UploadResult<TMeta>>
+    reset(): void
+    getID(): string
+    /**
+     * Add a plugin to this Uppy instance.
+     */
+    use<TOptions, TInstance extends Plugin<TOptions>>(
+      pluginClass: new (uppy: this, opts: TOptions) => TInstance,
+      opts?: TOptions
+    ): this
+    /**
+     * Fallback `.use()` overload with unchecked plugin options.
+     *
+     * This does not validate that the options you pass in are correct.
+     * We recommend disabling this overload by using the `Uppy<Uppy.StrictTypes>` type, instead of the plain `Uppy` type, to enforce strict typechecking.
+     * This overload will be removed in Uppy 2.0.
+     */
+    use(pluginClass: TUseStrictTypes extends StrictTypes ? never : new (uppy: this, opts: any) => Plugin<any>, opts?: object): this
+    getPlugin(name: string): Plugin
+    iteratePlugins(callback: (plugin: Plugin) => void): void
+    removePlugin(instance: Plugin): void
+    close(): void
+    info(
+      message: string | { message: string; details: string },
+      type?: LogLevel,
+      duration?: number
+    ): void
+    hideInfo(): void
+    log(msg: string, type?: LogLevel): void
+    /**
+     * Obsolete: do not use. This method does nothing and will be removed in a future release.
+     */
+    run(): this
+    restore<TMeta extends IndexedObject<any> = {}>(
+      uploadID: string
+    ): Promise<UploadResult<TMeta>>
+    addResultData(uploadID: string, data: object): void
+    upload<TMeta extends IndexedObject<any> = {}>(): Promise<UploadResult<TMeta>>
   }
 }
 
-declare function Uppy(opts?: Partial<Uppy.UppyOptions>): Uppy.Uppy;
-
-export = Uppy;
+/**
+ * Create an uppy instance.
+ *
+ * By default, Uppy's `.use(Plugin, options)` method uses loose type checking.
+ * In Uppy 2.0, the `.use()` method will get a stricter type signature. You can enable strict type checking of plugin classes and their options today by using:
+ * ```ts
+ * const uppy = Uppy<Uppy.StrictTypes>()
+ * ```
+ * Make sure to also declare any variables and class properties with the `StrictTypes` parameter:
+ * ```ts
+ * private uppy: Uppy<Uppy.StrictTypes>;
+ * ```
+ */
+declare function Uppy<TUseStrictTypes extends Uppy.TypeChecking = Uppy.TypeChecking>(opts?: Uppy.UppyOptions): Uppy.Uppy<TUseStrictTypes>
+
+export = Uppy

+ 91 - 0
packages/@uppy/core/types/index.test-d.ts

@@ -0,0 +1,91 @@
+import { expectError, expectType } from 'tsd'
+import Uppy = require('../')
+import DefaultStore = require('@uppy/store-default')
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  uppy.addFile({
+    data: new Blob([new ArrayBuffer(1024)], {
+      type: 'application/octet-stream'
+    })
+  })
+
+  uppy.upload().then(result => {
+    expectType<Uppy.UploadedUppyFile<{}, {}>>(result.successful[0])
+    expectType<Uppy.FailedUppyFile<{}, {}>>(result.failed[0])
+  })
+}
+
+{
+  const store = DefaultStore()
+  const uppy = Uppy<Uppy.StrictTypes>({ store })
+}
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  // this doesn't exist but type checking works anyway :)
+  const f = uppy.getFile('virtual')
+  if (f && f.progress && f.progress.uploadStarted === null) {
+    f.progress.uploadStarted = Date.now()
+  }
+
+  if (f && f.response && f.response.status === 200) {
+    expectType(f.response.body)
+  }
+  expectType<number>(f.response!.status)
+}
+
+{
+  type Meta = {}
+  type ResponseBody = {
+    averageColor: string
+  }
+  const uppy = Uppy<Uppy.StrictTypes>()
+  const f = uppy.getFile<Meta, ResponseBody>('virtual')!
+  expectType<ResponseBody>(f.response!.body)
+}
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  uppy.addFile({
+    name: 'empty.json',
+    data: new Blob(['null'], { type: 'application/json' }),
+    meta: { path: 'path/to/file' }
+  })
+}
+
+{
+  interface SomeOptions extends Uppy.PluginOptions {
+    types: 'are checked'
+  }
+  class SomePlugin extends Uppy.Plugin<SomeOptions> {}
+  const untypedUppy = Uppy()
+  untypedUppy.use(SomePlugin, { types: 'are unchecked' })
+  const typedUppy = Uppy<Uppy.StrictTypes>()
+  expectError(typedUppy.use(SomePlugin, { types: 'are unchecked' }))
+  typedUppy.use(SomePlugin, { types: 'are checked' })
+
+  // strictly-typed instance can be cast to a loosely-typed instance
+  const widenUppy: Uppy.Uppy = Uppy<Uppy.StrictTypes>()
+  // and disables the type checking
+  widenUppy.use(SomePlugin, { random: 'nonsense' })
+}
+
+{
+  const uppy = Uppy()
+  // can emit events with internal event types
+  uppy.emit('upload')
+  uppy.emit('complete', () => {})
+  uppy.emit('error', () => {})
+
+  // can emit events with custom event types
+  uppy.emit('dashboard:modal-closed', () => {})
+
+  // can register listners for internal events
+  uppy.on('upload', () => {})
+  uppy.on('complete', () => {})
+  uppy.on('error', () => {})
+
+  // can register listners on custom events
+  uppy.on('dashboard:modal-closed', () => {})
+}

+ 0 - 32
packages/@uppy/dashboard/types/dashboard-tests.ts

@@ -1,32 +0,0 @@
-import Uppy = require('@uppy/core')
-import Dashboard = require('../')
-
-{
-  const uppy = Uppy()
-  uppy.use(Dashboard, {
-    target: 'body'
-  })
-
-  const plugin = uppy.getPlugin('Dashboard') as Dashboard
-  plugin.openModal()
-  plugin.isModalOpen() // $ExpectType boolean
-  plugin.closeModal()
-}
-
-{
-  const uppy = Uppy()
-  uppy.use(Dashboard, <Partial<Dashboard.DashboardOptions>>{
-    width: '100%',
-    height: 700,
-    metaFields: [
-      { id: 'caption', name: 'Caption' },
-      { id: 'license', name: 'License', placeholder: 'Creative Commons, Apache 2.0, ...' },
-    ]
-  })
-}
-
-{
-  const uppy = Uppy()
-  // $ExpectError
-  uppy.use(Dashboard, { height: {} })
-}

+ 45 - 102
packages/@uppy/dashboard/types/index.d.ts

@@ -1,113 +1,56 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+import StatusBar = require('@uppy/status-bar')
+import DashboardLocale = require('./generatedLocale')
 
 interface MetaField {
-  id: string;
-  name: string;
-  placeholder?: string;
+  id: string
+  name: string
+  placeholder?: string
 }
 
 declare module Dashboard {
-  interface DashboardLocale {
-    strings: {
-      closeModal: string,
-      importFrom: string,
-      addingMoreFiles: string,
-      addMoreFiles: string,
-      dashboardWindowTitle: string,
-      dashboardTitle: string,
-      copyLinkToClipboardSuccess: string,
-      copyLinkToClipboardFallback: string,
-      copyLink: string,
-      link: string,
-      fileSource: string,
-      done: string,
-      back: string,
-      addMore: string,
-      removeFile: string,
-      editFile: string,
-      editing: string,
-      edit: string,
-      finishEditingFile: string,
-      saveChanges: string,
-      cancel: string,
-      myDevice: string,
-      dropPasteImport: string,
-      dropPaste: string,
-      dropHint: string,
-      browse: string,
-      uploadComplete: string,
-      uploadPaused: string,
-      resumeUpload: string,
-      pauseUpload: string,
-      retryUpload: string,
-      cancelUpload: string,
-      xFilesSelected: {
-        0: string,
-        1: string,
-        2: string
-      },
-      uploadingXFiles: {
-        0: string,
-        1: string,
-        2: string
-      },
-      processingXFiles: {
-        0: string,
-        1: string,
-        2: string
-      },
-      poweredBy: string
-    }
-  }
-
   interface DashboardOptions extends Uppy.PluginOptions {
-    animateOpenClose: boolean;
-    browserBackButtonClose: boolean
-    closeAfterFinish: boolean;
-    closeModalOnClickOutside: boolean;
-    disableInformer: boolean;
-    disablePageScrollWhenModalOpen: boolean;
-    disableStatusBar: boolean;
-    disableThumbnailGenerator: boolean;
-    height: string | number;
-    hideCancelButton: boolean;
-    hidePauseResumeButton: boolean;
-    hideProgressAfterFinish: boolean;
-    hideRetryButton: boolean;
-    hideUploadButton: boolean;
-    inline: boolean;
-    locale: DashboardLocale;
-    metaFields: MetaField[];
-    note: string | null;
-    onRequestCloseModal: () => void;
-    plugins: string[];
-    proudlyDisplayPoweredByUppy: boolean;
-    showLinkToFileUploadResult: boolean;
-    showProgressDetails: boolean;
-    showSelectedFiles: boolean;
-    target: string;
-    thumbnailWidth: number;
-    trigger: string;
-    width: string | number;
+    animateOpenClose?: boolean
+    browserBackButtonClose?: boolean
+    closeAfterFinish?: boolean
+    closeModalOnClickOutside?: boolean
+    disableInformer?: boolean
+    disablePageScrollWhenModalOpen?: boolean
+    disableStatusBar?: boolean
+    disableThumbnailGenerator?: boolean
+    height?: string | number
+    hideCancelButton?: boolean
+    hidePauseResumeButton?: boolean
+    hideProgressAfterFinish?: boolean
+    hideRetryButton?: boolean
+    hideUploadButton?: boolean
+    inline?: boolean
+    locale?: DashboardLocale & StatusBar.StatusBarLocale
+    metaFields?: MetaField[]
+    note?: string | null
+    onRequestCloseModal?: () => void
+    plugins?: string[]
+    proudlyDisplayPoweredByUppy?: boolean
+    showLinkToFileUploadResult?: boolean
+    showProgressDetails?: boolean
+    showSelectedFiles?: boolean
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    thumbnailWidth?: number
+    trigger?: string
+    width?: string | number
   }
 }
 
-declare class Dashboard extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<Dashboard.DashboardOptions>);
-  addTarget(plugin: Uppy.Plugin): HTMLElement;
-  hideAllPanels(): void;
-  openModal(): void;
-  closeModal(): void;
-  isModalOpen(): boolean;
-  render(state: object): void;
-  install(): void;
-  uninstall(): void;
+declare class Dashboard extends Uppy.Plugin<Dashboard.DashboardOptions> {
+  addTarget (plugin: Uppy.Plugin): HTMLElement
+  hideAllPanels (): void
+  openModal (): void
+  closeModal (): void
+  isModalOpen (): boolean
+  render (state: object): void
+  install (): void
+  uninstall (): void
 }
 
-export = Dashboard;
-
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof Dashboard, opts: Partial<Dashboard.DashboardOptions>): Uppy.Uppy;
-  }
-}
+export = Dashboard

+ 64 - 0
packages/@uppy/dashboard/types/index.test-d.ts

@@ -0,0 +1,64 @@
+import { expectType, expectError } from 'tsd'
+import Uppy = require('@uppy/core')
+import Dashboard = require('../')
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  uppy.use(Dashboard, {
+    target: 'body'
+  })
+
+  const plugin = uppy.getPlugin('Dashboard') as Dashboard
+  plugin.openModal()
+  expectType<boolean>(plugin.isModalOpen())
+  plugin.closeModal()
+}
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  uppy.use(Dashboard, {
+    width: '100%',
+    height: 700,
+    metaFields: [
+      { id: 'caption', name: 'Caption' },
+      {
+        id: 'license',
+        name: 'License',
+        placeholder: 'Creative Commons, Apache 2.0, ...'
+      }
+    ]
+  })
+}
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  uppy.use(Dashboard, {
+    locale: {
+      strings: {
+        // Dashboard string
+        addMoreFiles: 'yaddayadda',
+        // StatusBar string
+        uploading: '^^^^'
+      }
+    }
+  })
+  expectError(uppy.use(Dashboard, {
+    locale: {
+      strings: {
+        somethingThatDoesNotExist: 'wrong'
+      }
+    }
+  }))
+  const wrongType = 1234
+  expectError(uppy.use(Dashboard, {
+    locale: {
+      strings: {
+        addMoreFiles: wrongType
+      }
+    }
+  }))
+}
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  expectError(uppy.use(Dashboard, { height: {} }))
+}

+ 12 - 16
packages/@uppy/drag-drop/types/index.d.ts

@@ -1,23 +1,19 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+import DragDropLocale = require('./generatedLocale')
 
 declare module DragDrop {
   interface DragDropOptions extends Uppy.PluginOptions {
-    inputName: string;
-    allowMultipleFiles: boolean;
-    width: string;
-    height: string;
-    note: string;
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    inputName?: string
+    allowMultipleFiles?: boolean
+    width?: string
+    height?: string
+    note?: string
+    locale?: DragDropLocale
   }
 }
 
-declare class DragDrop extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<DragDrop.DragDropOptions>);
-}
-
-export = DragDrop;
+declare class DragDrop extends Uppy.Plugin<DragDrop.DragDropOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof DragDrop, opts: Partial<DragDrop.DragDropOptions>): Uppy.Uppy;
-  }
-}
+export = DragDrop

+ 2 - 0
packages/@uppy/drag-drop/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import DragDrop = require('../')
+// TODO implement

+ 11 - 15
packages/@uppy/dropbox/types/index.d.ts

@@ -1,21 +1,17 @@
-import Uppy = require('@uppy/core');
-import CompanionClient = require('@uppy/companion-client');
+import Uppy = require('@uppy/core')
+import CompanionClient = require('@uppy/companion-client')
 
 declare module Dropbox {
-  interface DropboxOptions extends Uppy.PluginOptions, CompanionClient.ProviderOptions {
-    companionUrl: string;
-    companionAllowedHosts: string | RegExp | Array<string | RegExp>;
+  interface DropboxOptions
+    extends Uppy.PluginOptions,
+      CompanionClient.PublicProviderOptions {
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    title?: string
+    storage?: CompanionClient.TokenStorage
   }
 }
 
-declare class Dropbox extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<Dropbox.DropboxOptions>);
-}
-
-export = Dropbox;
+declare class Dropbox extends Uppy.Plugin<Dropbox.DropboxOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof Dropbox, opts: Partial<Dropbox.DropboxOptions>): Uppy.Uppy;
-  }
-}
+export = Dropbox

+ 2 - 0
packages/@uppy/dropbox/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import Dropbox = require('../')
+// TODO implement

+ 11 - 15
packages/@uppy/facebook/types/index.d.ts

@@ -1,21 +1,17 @@
-import Uppy = require('@uppy/core');
-import CompanionClient = require('@uppy/companion-client');
+import Uppy = require('@uppy/core')
+import CompanionClient = require('@uppy/companion-client')
 
 declare module Facebook {
-  interface FacebookOptions extends Uppy.PluginOptions, CompanionClient.ProviderOptions {
-    companionUrl: string;
-    companionAllowedHosts: string | RegExp | Array<string | RegExp>;
+  interface FacebookOptions
+    extends Uppy.PluginOptions,
+      CompanionClient.PublicProviderOptions {
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    title?: string
+    storage?: CompanionClient.TokenStorage
   }
 }
 
-declare class Facebook extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<Facebook.FacebookOptions>);
-}
-
-export = Facebook;
+declare class Facebook extends Uppy.Plugin<Facebook.FacebookOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof Facebook, opts: Partial<Facebook.FacebookOptions>): Uppy.Uppy;
-  }
-}
+export = Facebook

+ 2 - 0
packages/@uppy/facebook/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import Facebook = require('../')
+// TODO implement

+ 10 - 14
packages/@uppy/file-input/types/index.d.ts

@@ -1,20 +1,16 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+import FileInputLocale = require('./generatedLocale')
 
 declare module FileInput {
-  interface FileInputOptions extends Uppy.PluginOptions {
-    pretty: boolean;
-    inputName: string;
+  export interface FileInputOptions extends Uppy.PluginOptions {
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    pretty?: boolean
+    inputName?: string
+    locale?: FileInputLocale
   }
 }
 
-declare class FileInput extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<FileInput.FileInputOptions>);
-}
-
-export = FileInput;
+declare class FileInput extends Uppy.Plugin<FileInput.FileInputOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof FileInput, opts: Partial<FileInput.FileInputOptions>): Uppy.Uppy;
-  }
-}
+export = FileInput

+ 1 - 0
packages/@uppy/file-input/types/index.test-d.ts

@@ -0,0 +1 @@
+import FileInput = require('../')

+ 1 - 1
packages/@uppy/form/src/index.js

@@ -29,7 +29,7 @@ module.exports = class Form extends Plugin {
     }
 
     // merge default options with the ones set by user
-    this.opts = Object.assign({}, defaultOptions, opts)
+    this.opts = { ...defaultOptions, ...opts }
 
     this.handleFormSubmit = this.handleFormSubmit.bind(this)
     this.handleUploadStart = this.handleUploadStart.bind(this)

+ 11 - 16
packages/@uppy/form/types/index.d.ts

@@ -1,23 +1,18 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
 
 declare module Form {
   interface FormOptions extends Uppy.PluginOptions {
-    getMetaFromForm: boolean;
-    addResultToForm: boolean;
-    submitOnSuccess: boolean;
-    triggerUploadOnSubmit: boolean;
-    resultName: string;
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    resultName?: string
+    getMetaFromForm?: boolean
+    addResultToForm?: boolean
+    multipleResults?: boolean
+    submitOnSuccess?: boolean
+    triggerUploadOnSubmit?: boolean
   }
 }
 
-declare class Form extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<Form.FormOptions>);
-}
-
-export = Form;
+declare class Form extends Uppy.Plugin<Form.FormOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof Form, opts: Partial<Form.FormOptions>): Uppy.Uppy;
-  }
-}
+export = Form

+ 1 - 0
packages/@uppy/form/types/index.test-d.ts

@@ -0,0 +1 @@
+import Form = require('../')

+ 8 - 14
packages/@uppy/golden-retriever/types/index.d.ts

@@ -1,21 +1,15 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
 
 declare module GoldenRetriever {
   interface GoldenRetrieverOptions extends Uppy.PluginOptions {
-    expires: number;
-    serviceWorker: boolean;
-    indexedDB: any;
+    expires?: number
+    serviceWorker?: boolean
+    indexedDB?: any
   }
 }
 
-declare class GoldenRetriever extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<GoldenRetriever.GoldenRetrieverOptions>);
-}
-
-export = GoldenRetriever;
+declare class GoldenRetriever extends Uppy.Plugin<
+  GoldenRetriever.GoldenRetrieverOptions
+> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof GoldenRetriever, opts: Partial<GoldenRetriever.GoldenRetrieverOptions>): Uppy.Uppy;
-  }
-}
+export = GoldenRetriever

+ 1 - 0
packages/@uppy/golden-retriever/types/index.test-d.ts

@@ -0,0 +1 @@
+import GoldenRetriever = require('../')

+ 11 - 15
packages/@uppy/google-drive/types/index.d.ts

@@ -1,21 +1,17 @@
-import Uppy = require('@uppy/core');
-import CompanionClient = require('@uppy/companion-client');
+import Uppy = require('@uppy/core')
+import CompanionClient = require('@uppy/companion-client')
 
 declare module GoogleDrive {
-  interface GoogleDriveOptions extends Uppy.PluginOptions, CompanionClient.ProviderOptions {
-    companionUrl: string;
-    companionAllowedHosts: string | RegExp | Array<string | RegExp>;
+  interface GoogleDriveOptions
+    extends Uppy.PluginOptions,
+      CompanionClient.PublicProviderOptions {
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    title?: string
+    storage?: CompanionClient.TokenStorage
   }
 }
 
-declare class GoogleDrive extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<GoogleDrive.GoogleDriveOptions>);
-}
-
-export = GoogleDrive;
+declare class GoogleDrive extends Uppy.Plugin<GoogleDrive.GoogleDriveOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof GoogleDrive, opts: Partial<GoogleDrive.GoogleDriveOptions>): Uppy.Uppy;
-  }
-}
+export = GoogleDrive

+ 9 - 0
packages/@uppy/google-drive/types/index.test-d.ts

@@ -0,0 +1,9 @@
+import Uppy = require('@uppy/core')
+import GoogleDrive = require('../')
+
+class SomePlugin extends Uppy.Plugin<{}> {}
+
+const uppy = Uppy<Uppy.StrictTypes>()
+uppy.use(GoogleDrive, { companionUrl: '' })
+uppy.use(GoogleDrive, { target: SomePlugin, companionUrl: '' })
+uppy.use(GoogleDrive, { target: document.querySelector('#gdrive')!, companionUrl: '' })

+ 5 - 19
packages/@uppy/informer/types/index.d.ts

@@ -1,26 +1,12 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
 
 declare module Informer {
-  interface Color {
-    bg: string | number;
-    text: string | number;
-  }
-
   interface InformerOptions extends Uppy.PluginOptions {
-    typeColors: {
-      [type: string]: Color
-    };
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
   }
 }
 
-declare class Informer extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<Informer.InformerOptions>);
-}
-
-export = Informer;
+declare class Informer extends Uppy.Plugin<Informer.InformerOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof Informer, opts: Partial<Informer.InformerOptions>): Uppy.Uppy;
-  }
-}
+export = Informer

+ 1 - 0
packages/@uppy/informer/types/index.test-d.ts

@@ -0,0 +1 @@
+import Informer = require('../')

+ 11 - 15
packages/@uppy/instagram/types/index.d.ts

@@ -1,21 +1,17 @@
-import Uppy = require('@uppy/core');
-import CompanionClient = require('@uppy/companion-client');
+import Uppy = require('@uppy/core')
+import CompanionClient = require('@uppy/companion-client')
 
 declare module Instagram {
-  interface InstagramOptions extends Uppy.PluginOptions, CompanionClient.ProviderOptions {
-    companionUrl: string;
-    companionAllowedHosts: string | RegExp | Array<string | RegExp>;
+  interface InstagramOptions
+    extends Uppy.PluginOptions,
+      CompanionClient.PublicProviderOptions {
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    title?: string
+    storage?: CompanionClient.TokenStorage
   }
 }
 
-declare class Instagram extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<Instagram.InstagramOptions>);
-}
-
-export = Instagram;
+declare class Instagram extends Uppy.Plugin<Instagram.InstagramOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof Instagram, opts: Partial<Instagram.InstagramOptions>): Uppy.Uppy;
-  }
-}
+export = Instagram

+ 2 - 0
packages/@uppy/instagram/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import Instagram = require('../')
+// TODO implement

+ 11 - 15
packages/@uppy/onedrive/types/index.d.ts

@@ -1,21 +1,17 @@
-import Uppy = require('@uppy/core');
-import CompanionClient = require('@uppy/companion-client');
+import Uppy = require('@uppy/core')
+import CompanionClient = require('@uppy/companion-client')
 
 declare module OneDrive {
-  interface OneDriveOptions extends Uppy.PluginOptions, CompanionClient.ProviderOptions {
-    companionUrl: string;
-    companionAllowedHosts: string | RegExp | Array<string | RegExp>;
+  interface OneDriveOptions
+    extends Uppy.PluginOptions,
+      CompanionClient.PublicProviderOptions {
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    title?: string
+    storage?: CompanionClient.TokenStorage
   }
 }
 
-declare class OneDrive extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<OneDrive.OneDriveOptions>);
-}
-
-export = OneDrive;
+declare class OneDrive extends Uppy.Plugin<OneDrive.OneDriveOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof OneDrive, opts: Partial<OneDrive.OneDriveOptions>): Uppy.Uppy;
-  }
-}
+export = OneDrive

+ 2 - 0
packages/@uppy/onedrive/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import OneDrive = require('../')
+// TODO implement

+ 7 - 13
packages/@uppy/progress-bar/types/index.d.ts

@@ -1,20 +1,14 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
 
 declare module ProgressBar {
   interface ProgressBarOptions extends Uppy.PluginOptions {
-    hideAfterFinish: boolean;
-    fixed: boolean;
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    hideAfterFinish?: boolean
+    fixed?: boolean
   }
 }
 
-declare class ProgressBar extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<ProgressBar.ProgressBarOptions>);
-}
-
-export = ProgressBar;
+declare class ProgressBar extends Uppy.Plugin<ProgressBar.ProgressBarOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof ProgressBar, opts: Partial<ProgressBar.ProgressBarOptions>): Uppy.Uppy;
-  }
-}
+export = ProgressBar

+ 2 - 0
packages/@uppy/progress-bar/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import ProgressBar = require('../')
+// TODO implement

+ 11 - 2
packages/@uppy/react/src/CommonTypes.d.ts

@@ -1,4 +1,13 @@
 import UppyCore = require('@uppy/core');
 
-export interface Uppy extends UppyCore.Uppy {}
-export interface Locale extends UppyCore.Locale {}
+export type Uppy = UppyCore.Uppy
+export type Locale = UppyCore.Locale
+
+// Helper to exclude a key
+export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
+
+// If I use the helper it doesn't work, I think because 'target' is not a `keyof T` while
+// the generic T is unknown, so will just use the expansion here
+type OmitTarget<T> = Pick<T, Exclude<keyof T, 'target' | 'replaceTargetContent'>>
+type WithBaseUppyProps<T> = T & { uppy: Uppy }
+export type ToUppyProps<T> = WithBaseUppyProps<OmitTarget<T>>

+ 13 - 32
packages/@uppy/react/src/Dashboard.d.ts

@@ -1,39 +1,20 @@
-import { Uppy, Locale } from './CommonTypes';
+import { Omit, ToUppyProps } from './CommonTypes'
+import Dashboard = require('@uppy/dashboard')
 
-interface MetaField {
-  id: string;
-  name: string;
-  placeholder?: string;
-}
+// This type is mapped into `DashboardProps` below so IntelliSense doesn't display this big mess of nested types
+type DashboardPropsInner = Omit<
+  ToUppyProps<Dashboard.DashboardOptions>,
+  // Remove the modal-only props
+  'animateOpenClose' | 'browserBackButtonClose' | 'inline' | 'onRequestCloseModal' | 'trigger'
+>
 
-export interface DashboardProps {
-  uppy: Uppy;
-  inline?: boolean;
-  plugins?: Array<string>;
-  trigger?: string;
-  width?: number;
-  height?: number;
-  showLinkToFileUploadResult?: boolean;
-  showProgressDetails?: boolean;
-  hideUploadButton?: boolean;
-  hideRetryButton?: boolean;
-  hidePauseResumeButton?: boolean;
-  hideCancelButton?: boolean;
-  hideProgressAfterFinish?: boolean;
-  showSelectedFiles?: boolean;
-  note?: string;
-  metaFields?: Array<MetaField>;
-  proudlyDisplayPoweredByUppy?: boolean;
-  disableStatusBar?: boolean;
-  disableInformer?: boolean;
-  disableThumbnailGenerator?: boolean;
-  thumbnailWidth?: number;
-  locale?: Locale;
+export type DashboardProps = {
+   [K in keyof DashboardPropsInner]: DashboardPropsInner[K]
 }
 
 /**
  * React Component that renders a Dashboard for an Uppy instance. This component
- * renders the Dashboard inline; so you can put it anywhere you want.
+ * renders the Dashboard inline so you can put it anywhere you want.
  */
-declare const Dashboard: React.ComponentType<DashboardProps>;
-export default Dashboard;
+declare const DashboardComponent: React.ComponentType<DashboardProps>
+export default DashboardComponent

+ 5 - 10
packages/@uppy/react/src/DashboardModal.d.ts

@@ -1,18 +1,13 @@
-import { DashboardProps } from './Dashboard';
+import { DashboardProps } from './Dashboard'
 
 export interface DashboardModalProps extends DashboardProps {
-  target?: string | HTMLElement;
-  open?: boolean;
-  onRequestClose?: VoidFunction;
-  closeAfterFinish?: boolean;
-  animateOpenClose?: boolean;
-  closeModalOnClickOutside?: boolean;
-  disablePageScrollWhenModalOpen?: boolean;
+  open?: boolean
+  onRequestClose?: VoidFunction
 }
 
 /**
  * React Component that renders a Dashboard for an Uppy instance in a Modal
  * dialog. Visibility of the Modal is toggled using the `open` prop.
  */
-declare const DashboardModal: React.ComponentType<DashboardModalProps>;
-export default DashboardModal;
+declare const DashboardModal: React.ComponentType<DashboardModalProps>
+export default DashboardModal

+ 5 - 7
packages/@uppy/react/src/DragDrop.d.ts

@@ -1,13 +1,11 @@
-import { Uppy, Locale } from './CommonTypes';
+import { ToUppyProps } from './CommonTypes'
+import DragDrop = require('@uppy/drag-drop')
 
-export interface DragDropProps {
-  uppy: Uppy;
-  locale?: Locale;
-}
+export type DragDropProps = ToUppyProps<DragDrop.DragDropOptions>
 
 /**
  * React component that renders an area in which files can be dropped to be
  * uploaded.
  */
-declare const DragDrop: React.ComponentType<DragDropProps>;
-export default DragDrop;
+declare const DragDropComponent: React.ComponentType<DragDropProps>;
+export default DragDropComponent;

+ 5 - 8
packages/@uppy/react/src/ProgressBar.d.ts

@@ -1,13 +1,10 @@
-import { Uppy } from './CommonTypes';
+import { ToUppyProps } from './CommonTypes'
+import ProgressBar = require('@uppy/progress-bar')
 
-export interface ProgressBarProps {
-  uppy: Uppy;
-  fixed?: boolean;
-  hideAfterFinish?: boolean;
-}
+export type ProgressBarProps = ToUppyProps<ProgressBar.ProgressBarOptions>
 
 /**
  * React component that renders a progress bar at the top of the page.
  */
-declare const ProgressBar: React.ComponentType<ProgressBarProps>;
-export default ProgressBar;
+declare const ProgressBarComponent: React.ComponentType<ProgressBarProps>
+export default ProgressBarComponent

+ 5 - 8
packages/@uppy/react/src/StatusBar.d.ts

@@ -1,14 +1,11 @@
-import { Uppy } from './CommonTypes';
+import { ToUppyProps } from './CommonTypes'
+import StatusBar = require('@uppy/status-bar')
 
-export interface StatusBarProps {
-  uppy: Uppy;
-  showProgressDetails?: boolean;
-  hideAfterFinish?: boolean;
-}
+export type StatusBarProps = ToUppyProps<StatusBar.StatusBarOptions>
 
 /**
  * React component that renders a status bar containing upload progress and speed,
  * processing progress and pause/resume/cancel controls.
  */
-declare const StatusBar: React.ComponentType<StatusBarProps>;
-export default StatusBar;
+declare const StatusBarComponent: React.ComponentType<StatusBarProps>
+export default StatusBarComponent

+ 6 - 6
packages/@uppy/react/types/index.d.ts

@@ -1,7 +1,7 @@
-import * as React from 'react';
+import * as React from 'react'
 
-export { default as Dashboard } from '../src/Dashboard';
-export { default as DashboardModal } from '../src/DashboardModal';
-export { default as DragDrop } from '../src/DragDrop';
-export { default as ProgressBar } from '../src/ProgressBar';
-export { default as StatusBar } from '../src/StatusBar';
+export { default as Dashboard } from '../src/Dashboard'
+export { default as DashboardModal } from '../src/DashboardModal'
+export { default as DragDrop } from '../src/DragDrop'
+export { default as ProgressBar } from '../src/ProgressBar'
+export { default as StatusBar } from '../src/StatusBar'

+ 20 - 0
packages/@uppy/react/types/index.test-d.tsx

@@ -0,0 +1,20 @@
+import React = require('react')
+import Uppy = require('@uppy/core')
+import { expectError } from 'tsd'
+import * as components from '../'
+
+const uppy = Uppy<Uppy.StrictTypes>()
+
+function TestComponent() {
+    return (
+        <components.Dashboard
+            uppy={uppy}
+            closeAfterFinish
+            hideCancelButton
+        />
+    )
+}
+
+// inline option should be removed from proptypes because it is always overridden
+// by the component
+expectError(<components.Dashboard inline />)

+ 6 - 13
packages/@uppy/redux-dev-tools/types/index.d.ts

@@ -1,18 +1,11 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
 
 declare module ReduxDevTools {
-  interface ReduxDevToolsOptions extends Uppy.PluginOptions {
-  }
+  interface ReduxDevToolsOptions extends Uppy.PluginOptions {}
 }
 
-declare class ReduxDevTools extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<ReduxDevTools.ReduxDevToolsOptions>);
-}
-
-export = ReduxDevTools;
+declare class ReduxDevTools extends Uppy.Plugin<
+  ReduxDevTools.ReduxDevToolsOptions
+> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof ReduxDevTools, opts: Partial<ReduxDevTools.ReduxDevToolsOptions>): Uppy.Uppy;
-  }
-}
+export = ReduxDevTools

+ 1 - 0
packages/@uppy/redux-dev-tools/types/index.test-d.ts

@@ -0,0 +1 @@
+import ReduxDevTools = require('../')

+ 12 - 14
packages/@uppy/status-bar/types/index.d.ts

@@ -1,21 +1,19 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+import GeneratedLocale = require('./generatedLocale')
 
 declare module StatusBar {
+  export type StatusBarLocale = GeneratedLocale
+
   export interface StatusBarOptions extends Uppy.PluginOptions {
-    showProgressDetails: boolean;
-    hideUploadButton: boolean;
-    hideAfterFinish: boolean;
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    showProgressDetails?: boolean
+    hideUploadButton?: boolean
+    hideAfterFinish?: boolean
+    locale?: StatusBarLocale
   }
 }
 
-declare class StatusBar extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<StatusBar.StatusBarOptions>);
-}
-
-export = StatusBar;
+declare class StatusBar extends Uppy.Plugin<StatusBar.StatusBarOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof StatusBar, opts: Partial<StatusBar.StatusBarOptions>): Uppy.Uppy;
-  }
-}
+export = StatusBar

+ 2 - 0
packages/@uppy/status-bar/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import StatusBar = require('../')
+// TODO implement

+ 7 - 7
packages/@uppy/store-default/types/index.d.ts

@@ -1,11 +1,11 @@
-import UppyUtils = require('@uppy/utils');
+import UppyUtils = require('@uppy/utils')
 
 declare class DefaultStore implements UppyUtils.Store {
-  constructor();
-  getState(): object;
-  setState(patch: object): void;
-  subscribe(listener: any): () => void;
+  constructor ()
+  getState (): object
+  setState (patch: object): void
+  subscribe (listener: any): () => void
 }
 
-declare function createDefaultStore(): DefaultStore;
-export = createDefaultStore;
+declare function createDefaultStore (): DefaultStore
+export = createDefaultStore

+ 6 - 0
packages/@uppy/store-default/types/index.test-d.ts

@@ -0,0 +1,6 @@
+import DefaultStore = require('../')
+
+const store = DefaultStore()
+
+store.setState({ a: 'b' })
+store.getState()

+ 0 - 6
packages/@uppy/store-default/types/store-default-tests.ts

@@ -1,6 +0,0 @@
-import DefaultStore = require('../');
-
-const store = DefaultStore();
-
-store.setState({ a: 'b' });
-store.getState();

+ 16 - 14
packages/@uppy/store-redux/types/index.d.ts

@@ -1,24 +1,26 @@
-import UppyUtils = require('@uppy/utils');
-import { Reducer, Middleware, Store as Redux } from 'redux';
+import UppyUtils = require('@uppy/utils')
+import { Reducer, Middleware, Store as Redux } from 'redux'
 
 declare namespace ReduxStore {
   interface ReduxStoreOptions {
-    store: Redux<object>;
-    id?: string;
-    selector?: (state: any) => object;
+    store: Redux<object>
+    id?: string
+    selector?: (state: any) => object
   }
 
   interface ReduxStore extends UppyUtils.Store {
-    constructor(opts: ReduxStoreOptions): ReduxStore;
-    getState(): object;
-    setState(patch: object): void;
-    subscribe(listener: any): () => void;
+    constructor (opts: ReduxStoreOptions): ReduxStore
+    getState (): object
+    setState (patch: object): void
+    subscribe (listener: any): () => void
   }
 
-  const reducer: Reducer<object>;
-  const middleware: Middleware;
-  const STATE_UPDATE: string;
+  const reducer: Reducer<object>
+  const middleware: Middleware
+  const STATE_UPDATE: string
 }
-declare function ReduxStore(opts: ReduxStore.ReduxStoreOptions): ReduxStore.ReduxStore;
+declare function ReduxStore (
+  opts: ReduxStore.ReduxStoreOptions
+): ReduxStore.ReduxStore
 
-export = ReduxStore;
+export = ReduxStore

+ 13 - 0
packages/@uppy/store-redux/types/index.test-d.ts

@@ -0,0 +1,13 @@
+import { createStore, combineReducers } from 'redux'
+import ReduxStore = require('../')
+
+const reducer = combineReducers({
+  uppy: ReduxStore.reducer
+})
+
+const store = ReduxStore({
+  store: createStore(reducer)
+})
+
+store.setState({ a: 1 })
+store.getState()

+ 0 - 13
packages/@uppy/store-redux/types/store-redux-tests.ts

@@ -1,13 +0,0 @@
-import { createStore, combineReducers } from 'redux';
-import ReduxStore = require('../');
-
-const reducer = combineReducers({
-  uppy: ReduxStore.reducer
-});
-
-const store = ReduxStore({
-  store: createStore(reducer)
-});
-
-store.setState({ a: 1 });
-store.getState();

+ 8 - 12
packages/@uppy/thumbnail-generator/types/index.d.ts

@@ -1,19 +1,15 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+import ThumbnailGeneratorLocale = require('./generatedLocale')
 
 declare module ThumbnailGenerator {
   interface ThumbnailGeneratorOptions extends Uppy.PluginOptions {
-    thumbnailWidth: number;
+    thumbnailWidth?: number
+    locale?: ThumbnailGeneratorLocale
   }
 }
 
-declare class ThumbnailGenerator extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<ThumbnailGenerator.ThumbnailGeneratorOptions>);
-}
-
-export = ThumbnailGenerator;
+declare class ThumbnailGenerator extends Uppy.Plugin<
+  ThumbnailGenerator.ThumbnailGeneratorOptions
+> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof ThumbnailGenerator, opts: Partial<ThumbnailGenerator.ThumbnailGeneratorOptions>): Uppy.Uppy;
-  }
-}
+export = ThumbnailGenerator

+ 2 - 0
packages/@uppy/thumbnail-generator/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import ThumbnailGenerator = require('../')
+// TODO implement

+ 30 - 27
packages/@uppy/transloadit/types/index.d.ts

@@ -1,40 +1,43 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+import TransloaditLocale = require('./generatedLocale')
 
 declare module Transloadit {
   interface AssemblyParameters {
-    auth: { key: string };
-    template_id?: string;
-    steps?: { [step: string]: object };
-    notify_url?: string;
-    fields?: { [name: string]: number | string };
+    auth: { key: string }
+    template_id?: string
+    steps?: { [step: string]: object }
+    notify_url?: string
+    fields?: { [name: string]: number | string }
   }
 
   interface AssemblyOptions {
-    params: AssemblyParameters;
-    fields?: { [name: string]: number | string };
-    signature?: string;
+    params: AssemblyParameters
+    fields?: { [name: string]: number | string }
+    signature?: string
   }
 
-  interface TransloaditOptions extends Uppy.PluginOptions {
-    params: AssemblyParameters;
-    signature: string;
-    service: string;
-    waitForEncoding: boolean;
-    waitForMetadata: boolean;
-    importFromUploadURLs: boolean;
-    alwaysRunAssembly: boolean;
-    getAssemblyOptions: (file: Uppy.UppyFile) => AssemblyOptions | Promise<AssemblyOptions>;
+  interface TransloaditOptionsBase extends Uppy.PluginOptions {
+    service?: string
+    errorReporting?: boolean
+    waitForEncoding?: boolean
+    waitForMetadata?: boolean
+    importFromUploadURLs?: boolean
+    alwaysRunAssembly?: boolean
+    locale?: TransloaditLocale
+    limit?: number
   }
-}
 
-declare class Transloadit extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<Transloadit.TransloaditOptions>);
+  // Either have a getAssemblyOptions() that returns an AssemblyOptions, *or* have them embedded in the options
+  type TransloaditOptions = TransloaditOptionsBase &
+    (
+      | {
+          getAssemblyOptions?: (
+            file: Uppy.UppyFile
+          ) => AssemblyOptions | Promise<AssemblyOptions>
+        }
+      | AssemblyOptions)
 }
 
-export = Transloadit;
+declare class Transloadit extends Uppy.Plugin<Transloadit.TransloaditOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof Transloadit, opts: Partial<Transloadit.TransloaditOptions>): Uppy.Uppy;
-  }
-}
+export = Transloadit

+ 63 - 0
packages/@uppy/transloadit/types/index.test-d.ts

@@ -0,0 +1,63 @@
+import { expectError, expectType } from 'tsd'
+import Uppy = require('@uppy/core')
+import Transloadit = require('../')
+
+const validParams = {
+  auth: { key: 'not so secret key' }
+}
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  uppy.use(Transloadit, {
+    getAssemblyOptions (file) {
+      expectType<Uppy.UppyFile>(file)
+      return { params: validParams }
+    },
+    waitForEncoding: false,
+    waitForMetadata: true,
+    importFromUploadURLs: false,
+    params: {
+      auth: { key: 'abc' },
+      steps: {}
+    }
+  })
+}
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  // must be bools
+  expectError(
+    uppy.use(Transloadit, { waitForEncoding: null, params: validParams })
+  )
+  expectError(
+    uppy.use(Transloadit, { waitForMetadata: null, params: validParams })
+  )
+}
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  // params.auth.key must be string
+  expectError(uppy.use(Transloadit, { params: {} }))
+  expectError(uppy.use(Transloadit, { params: { auth: {} } }))
+  expectError(
+    uppy.use(Transloadit, {
+      params: {
+        auth: { key: null }
+      }
+    })
+  )
+  expectError(
+    uppy.use(Transloadit, {
+      params: {
+        auth: { key: 'abc' },
+        steps: 'test'
+      }
+    })
+  )
+  uppy.use(Transloadit, {
+    params: {
+      auth: { key: 'abc' },
+      steps: { name: {} }
+    }
+  })
+}

+ 0 - 53
packages/@uppy/transloadit/types/transloadit-tests.ts

@@ -1,53 +0,0 @@
-import Uppy = require('@uppy/core')
-import Transloadit = require('../')
-
-{
-  const uppy = Uppy()
-  uppy.use(Transloadit, {
-    getAssemblyOptions(file) {
-      file // $ExpectType Uppy.UppyFile
-    },
-    waitForEncoding: false,
-    waitForMetadata: true,
-    importFromUploadURLs: false,
-    params: {
-      auth: { key: 'abc' },
-      steps: {}
-    }
-  })
-}
-
-{
-  const uppy = Uppy()
-  // $ExpectError
-  uppy.use(Transloadit, { waitForEncoding: null })
-  // $ExpectError
-  uppy.use(Transloadit, { waitForMetadata: null })
-}
-
-{
-  const uppy = Uppy()
-  // $ExpectError
-  uppy.use(Transloadit, { params: {} })
-  // $ExpectError
-  uppy.use(Transloadit, { params: { auth: {} } })
-  // $ExpectError
-  uppy.use(Transloadit, {
-    params: {
-      auth: { key: null }
-    }
-  })
-  // $ExpectError
-  uppy.use(Transloadit, {
-    params: {
-      auth: { key: 'abc' },
-      steps: 'test'
-    }
-  })
-  uppy.use(Transloadit, {
-    params: {
-      auth: { key: 'abc' },
-      steps: { name: {} }
-    }
-  })
-}

+ 1 - 0
packages/@uppy/tus/package.json

@@ -22,6 +22,7 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
+    "@types/tus-js-client": "^1.8.0",
     "@uppy/companion-client": "file:../companion-client",
     "@uppy/utils": "file:../utils",
     "tus-js-client": "^1.8.0"

+ 19 - 23
packages/@uppy/tus/types/index.d.ts

@@ -1,29 +1,25 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+import { UploadOptions } from 'tus-js-client'
 
 declare module Tus {
-  export interface TusOptions extends Uppy.PluginOptions {
-    resume: boolean;
-    removeFingerprintOnSuccess: boolean;
-    endpoint: string;
-    headers: object;
-    chunkSize: number;
-    withCredentials: boolean;
-    overridePatchMethod: boolean;
-    retryDelays: number[];
-    metaFields: string[] | null;
-    autoRetry: boolean;
-    limit: number;
-  }
-}
+  type TusUploadOptions = Pick<UploadOptions, Exclude<keyof UploadOptions,
+    | 'fingerprint'
+    | 'metadata'
+    | 'onProgress'
+    | 'onChunkComplete'
+    | 'onSuccess'
+    | 'onError'
+    | 'uploadUrl'
+    | 'uploadSize'
+  >>
 
-declare class Tus extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<Tus.TusOptions>);
+  export interface TusOptions extends Uppy.PluginOptions, TusUploadOptions {
+    metaFields?: string[] | null
+    autoRetry?: boolean
+    limit?: number
+  }
 }
 
-export = Tus;
+declare class Tus extends Uppy.Plugin<Tus.TusOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof Tus, opts: Partial<Tus.TusOptions>): Uppy.Uppy;
-  }
-}
+export = Tus

+ 1 - 0
packages/@uppy/tus/types/index.test-d.ts

@@ -0,0 +1 @@
+import Tus = require('../')

+ 12 - 14
packages/@uppy/url/types/index.d.ts

@@ -1,20 +1,18 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+import CompanionClient = require('@uppy/companion-client')
+import UrlLocale = require('./generatedLocale')
 
 declare module Url {
-  export interface UrlOptions extends Uppy.PluginOptions {
-    companionUrl: string;
-    // TODO inherit from ProviderOptions
+  export interface UrlOptions
+    extends Uppy.PluginOptions,
+      CompanionClient.RequestClientOptions {
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    title?: string
+    locale?: UrlLocale
   }
 }
 
-declare class Url extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<Url.UrlOptions>);
-}
-
-export = Url;
+declare class Url extends Uppy.Plugin<Url.UrlOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof Url, opts: Partial<Url.UrlOptions>): Uppy.Uppy;
-  }
-}
+export = Url

+ 2 - 0
packages/@uppy/url/types/index.test-d.ts

@@ -0,0 +1,2 @@
+import Url = require('../')
+// TODO implement

+ 117 - 85
packages/@uppy/utils/types/index.d.ts

@@ -3,15 +3,15 @@ declare module '@uppy/utils/lib/Translator' {
     export interface TranslatorOptions {
       locale: {
         strings: {
-          [key: string]: string | { [plural: number]: string };
-        };
-        pluralize: (n: number) => number;
-      };
+          [key: string]: string | { [plural: number]: string }
+        }
+        pluralize: (n: number) => number
+      }
     }
   }
 
   class Translator {
-    constructor(opts: Translator.TranslatorOptions);
+    constructor (opts: Translator.TranslatorOptions)
   }
 
   export = Translator
@@ -19,17 +19,17 @@ declare module '@uppy/utils/lib/Translator' {
 
 declare module '@uppy/utils/lib/EventTracker' {
   namespace EventTracker {
-    export type EventHandler = (...args: any[]) => void;
+    export type EventHandler = (...args: any[]) => void
     export interface Emitter {
-      on: (event: string, handler: EventHandler) => void;
-      off: (event: string, handler: EventHandler) => void;
+      on: (event: string, handler: EventHandler) => void
+      off: (event: string, handler: EventHandler) => void
     }
   }
 
   class EventTracker {
-    constructor(emitter: EventTracker.Emitter);
-    on(event: string, handler: EventTracker.EventHandler): void;
-    remove(): void;
+    constructor (emitter: EventTracker.Emitter)
+    on (event: string, handler: EventTracker.EventHandler): void
+    remove (): void
   }
 
   export = EventTracker
@@ -37,208 +37,240 @@ declare module '@uppy/utils/lib/EventTracker' {
 
 declare module '@uppy/utils/lib/ProgressTimeout' {
   class ProgressTimeout {
-    constructor(timeout: number, timeoutHandler: () => void);
-    progress(): void;
-    done(): void;
+    constructor (timeout: number, timeoutHandler: () => void)
+    progress (): void
+    done (): void
   }
   export = ProgressTimeout
 }
 
 declare module '@uppy/utils/lib/RateLimitedQueue' {
   namespace RateLimitedQueue {
-    export type AbortFunction = () => void;
-    export type PromiseFunction = (...args: any[]) => Promise<any>;
+    export type AbortFunction = () => void
+    export type PromiseFunction = (...args: any[]) => Promise<any>
     export type QueueEntry = {
-      abort: () => void,
-      done: () => void,
-    };
+      abort: () => void
+      done: () => void
+    }
   }
 
   class RateLimitedQueue {
-    constructor(limit: number);
-    run(fn: () => RateLimitedQueue.AbortFunction): RateLimitedQueue.QueueEntry;
-    wrapPromiseFunction(fn: () => RateLimitedQueue.PromiseFunction): RateLimitedQueue.PromiseFunction;
+    constructor (limit: number)
+    run (fn: () => RateLimitedQueue.AbortFunction): RateLimitedQueue.QueueEntry
+    wrapPromiseFunction(
+      fn: () => RateLimitedQueue.PromiseFunction
+    ): RateLimitedQueue.PromiseFunction
   }
 
   export = RateLimitedQueue
 }
 
 declare module '@uppy/utils/lib/canvasToBlob' {
-  function canvasToBlob(canvas: HTMLCanvasElement, type: string, quality?: number): Promise<Blob>;
+  function canvasToBlob (
+    canvas: HTMLCanvasElement,
+    type: string,
+    quality?: number
+  ): Promise<Blob>
   export = canvasToBlob
 }
 
 declare module '@uppy/utils/lib/dataURItoBlob' {
-  function dataURItoBlob(dataURI: string, opts: { mimeType?: string, name?: string }): Blob;
+  function dataURItoBlob (
+    dataURI: string,
+    opts: { mimeType?: string; name?: string }
+  ): Blob
   export = dataURItoBlob
 }
 
 declare module '@uppy/utils/lib/dataURItoFile' {
-  function dataURItoFile(dataURI: string, opts: { mimeType?: string, name?: string }): File;
+  function dataURItoFile (
+    dataURI: string,
+    opts: { mimeType?: string; name?: string }
+  ): File
   export = dataURItoFile
 }
 
 declare module '@uppy/utils/lib/emitSocketProgress' {
-  import UppyUtils = require('@uppy/utils');
+  import UppyUtils = require('@uppy/utils')
 
   interface ProgressData {
-    progress: number;
-    bytesUploaded: number;
-    bytesTotal: number;
+    progress: number
+    bytesUploaded: number
+    bytesTotal: number
   }
 
-  function emitSocketProgress(uploader: object, progressData: ProgressData, file: UppyUtils.UppyFile): void;
+  function emitSocketProgress (
+    uploader: object,
+    progressData: ProgressData,
+    file: UppyUtils.UppyFile
+  ): void
   export = emitSocketProgress
 }
 
 declare module '@uppy/utils/lib/findAllDOMElements' {
-  function findAllDOMElements(element: string | HTMLElement): HTMLElement[];
+  function findAllDOMElements (element: string | HTMLElement): HTMLElement[]
   export = findAllDOMElements
 }
 
 declare module '@uppy/utils/lib/findDOMElement' {
-  function findDOMElement(element: string | HTMLElement): HTMLElement | null;
+  function findDOMElement (element: string | HTMLElement): HTMLElement | null
   export = findDOMElement
 }
 
 declare module '@uppy/utils/lib/generateFileID' {
-  import UppyUtils = require('@uppy/utils');
+  import UppyUtils = require('@uppy/utils')
 
-  function generateFileID(file: UppyUtils.UppyFile): string;
+  function generateFileID (file: UppyUtils.UppyFile): string
   export = generateFileID
 }
 
 declare module '@uppy/utils/lib/getBytesRemaining' {
-  function getBytesRemaining(progress: { bytesTotal: number, bytesUploaded: number }): number;
+  function getBytesRemaining (progress: {
+    bytesTotal: number
+    bytesUploaded: number
+  }): number
   export = getBytesRemaining
 }
 
 declare module '@uppy/utils/lib/getETA' {
-  function getETA(progress: object): number;
+  function getETA (progress: object): number
   export = getETA
 }
 
 declare module '@uppy/utils/lib/getFileNameAndExtension' {
-  function getFileNameAndExtension(filename: string): { name: string, extension: string | undefined };
+  function getFileNameAndExtension(
+    filename: string
+  ): { name: string, extension: string | undefined };
   export = getFileNameAndExtension
 }
 
 declare module '@uppy/utils/lib/getFileType' {
-  import UppyUtils = require('@uppy/utils');
+  import UppyUtils = require('@uppy/utils')
 
-  function getFileType(file: UppyUtils.UppyFile): string | null;
+  function getFileType (file: UppyUtils.UppyFile): string | null
   export = getFileType
 }
 
 declare module '@uppy/utils/lib/getFileTypeExtension' {
-  function getFileTypeExtension(mime: string): string;
+  function getFileTypeExtension (mime: string): string
   export = getFileTypeExtension
 }
 
 declare module '@uppy/utils/lib/getSocketHost' {
-  function getSocketHost(url: string): string;
+  function getSocketHost (url: string): string
   export = getSocketHost
 }
 
 declare module '@uppy/utils/lib/getSpeed' {
-  function getSpeed(progress: { bytesTotal: number, bytesUploaded: number }): number;
+  function getSpeed (progress: {
+    bytesTotal: number
+    bytesUploaded: number
+  }): number
   export = getSpeed
 }
 
 declare module '@uppy/utils/lib/getTimeStamp' {
-  function getTimeStamp(): string;
+  function getTimeStamp (): string
   export = getTimeStamp
 }
 
 declare module '@uppy/utils/lib/isDOMElement' {
-  function isDOMElement(element: any): boolean;
+  function isDOMElement (element: any): boolean
   export = isDOMElement
 }
 
 declare module '@uppy/utils/lib/isObjectURL' {
-  function isObjectURL(url: string): boolean;
+  function isObjectURL (url: string): boolean
   export = isObjectURL
 }
 
 declare module '@uppy/utils/lib/isDragDropSupported' {
-  function isDragDropSupported(): boolean;
+  function isDragDropSupported (): boolean
   export = isDragDropSupported
 }
 
 declare module '@uppy/utils/lib/isPreviewSupported' {
-  function isPreviewSupported(mime: string): boolean;
+  function isPreviewSupported (mime: string): boolean
   export = isPreviewSupported
 }
 
 declare module '@uppy/utils/lib/isTouchDevice' {
-  function isTouchDevice(): boolean;
+  function isTouchDevice (): boolean
   export = isTouchDevice
 }
 
 declare module '@uppy/utils/lib/prettyETA' {
-  function prettyETA(seconds: number): string;
+  function prettyETA (seconds: number): string
   export = prettyETA
 }
 
 declare module '@uppy/utils/lib/secondsToTime' {
-  function secondsToTime(seconds: number): string;
+  function secondsToTime (seconds: number): string
   export = secondsToTime
 }
 
 declare module '@uppy/utils/lib/settle' {
-  function settle<T>(promises: Promise<T>[]): Promise<{ successful: T[], failed: any[] }>;
+  function settle<T> (
+    promises: Promise<T>[]
+  ): Promise<{ successful: T[]; failed: any[] }>
   export = settle
 }
 
 declare module '@uppy/utils/lib/toArray' {
-  function toArray(list: any): any[];
+  function toArray (list: any): any[]
   export = toArray
 }
 
 declare module '@uppy/utils/lib/getDroppedFiles' {
-  function getDroppedFiles(dataTransfer: DataTransfer, options?: object): Promise<File[]>;
+  function getDroppedFiles (
+    dataTransfer: DataTransfer,
+    options?: object
+  ): Promise<File[]>
   export = getDroppedFiles
 }
 
 declare module '@uppy/utils' {
   interface IndexedObject<T> {
-    [key: string]: T;
-    [key: number]: T;
+    [key: string]: T
+    [key: number]: T
   }
-  export type InternalMetadata = { name: string, type?: string };
-  export interface UppyFile<TMeta = IndexedObject<any>, TBody = IndexedObject<any>> {
-    data: Blob | File;
-    extension: string;
-    id: string;
-    isPaused?: boolean;
-    isRemote: boolean;
-    meta: InternalMetadata & TMeta;
-    name: string;
-    preview?: string;
+  export type InternalMetadata = { name: string; type?: string }
+  export interface UppyFile<
+    TMeta = IndexedObject<any>,
+    TBody = IndexedObject<any>
+  > {
+    data: Blob | File
+    extension: string
+    id: string
+    isPaused?: boolean
+    isRemote: boolean
+    meta: InternalMetadata & TMeta
+    name: string
+    preview?: string
     progress?: {
-      uploadStarted: number | null;
-      uploadComplete: boolean;
-      percentage: number;
-      bytesUploaded: number;
-      bytesTotal: number;
-    };
+      uploadStarted: number | null
+      uploadComplete: boolean
+      percentage: number
+      bytesUploaded: number
+      bytesTotal: number
+    }
     remote?: {
-      host: string;
-      url: string;
-      body?: object;
-    };
-    size: number;
-    source?: string;
-    type?: string;
+      host: string
+      url: string
+      body?: object
+    }
+    size: number
+    source?: string
+    type?: string
     response?: {
-      body: TBody;
-      status: number;
-      uploadURL: string | undefined;
-    };
+      body: TBody
+      status: number
+      uploadURL: string | undefined
+    }
   }
   export interface Store {
-    getState(): object;
-    setState(patch: object): void;
-    subscribe(listener: any): () => void;
+    getState (): object
+    setState (patch: object): void
+    subscribe (listener: any): () => void
   }
 }

+ 1 - 0
packages/@uppy/utils/types/index.test-d.ts

@@ -0,0 +1 @@
+// TODO implement

+ 18 - 17
packages/@uppy/webcam/types/index.d.ts

@@ -1,25 +1,26 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+import WebcamLocale = require('./generatedLocale')
 
 declare module Webcam {
-  export type WebcamMode = 'video-audio' | 'video-only' | 'audio-only' | 'picture';
+  export type WebcamMode =
+    | 'video-audio'
+    | 'video-only'
+    | 'audio-only'
+    | 'picture'
 
   export interface WebcamOptions extends Uppy.PluginOptions {
-    onBeforeSnapshot?: () => Promise<void>;
-    countdown?: number | boolean;
-    mirror?: boolean;
-    facingMode?: string;
-    modes: WebcamMode[];
+    replaceTargetContent?: boolean
+    target?: Uppy.PluginTarget
+    onBeforeSnapshot?: () => Promise<void>
+    countdown?: number | boolean
+    mirror?: boolean
+    facingMode?: string
+    modes?: WebcamMode[]
+    locale?: WebcamLocale
+    title?: string
   }
 }
 
-declare class Webcam extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: Partial<Webcam.WebcamOptions>);
-}
-
-export = Webcam;
+declare class Webcam extends Uppy.Plugin<Webcam.WebcamOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof Webcam, opts: Partial<Webcam.WebcamOptions>): Uppy.Uppy;
-  }
-}
+export = Webcam

+ 8 - 0
packages/@uppy/webcam/types/index.test-d.ts

@@ -0,0 +1,8 @@
+import Uppy = require('@uppy/core')
+import Webcam = require('../')
+
+{
+  Uppy<Uppy.StrictTypes>().use(Webcam, {
+    modes: ['video-only']
+  })
+}

+ 0 - 8
packages/@uppy/webcam/types/webcam-tests.ts

@@ -1,8 +0,0 @@
-import Uppy = require('@uppy/core');
-import Webcam = require('../');
-
-{
-  Uppy().use(Webcam, {
-    modes: ['video-only']
-  });
-}

+ 15 - 21
packages/@uppy/xhr-upload/types/index.d.ts

@@ -1,28 +1,22 @@
-import Uppy = require('@uppy/core');
+import Uppy = require('@uppy/core')
+import XHRUploadLocale = require('./generatedLocale')
 
 declare module XHRUpload {
   export interface XHRUploadOptions extends Uppy.PluginOptions {
-    limit?: number;
-    bundle?: boolean;
-    formData?: FormData;
-    headers?: any;
-    metaFields?: string[];
-    fieldName?: string;
-    timeout?: number;
-    responseUrlFieldName?: string;
-    endpoint: string;
-    method?: 'GET' | 'POST' | 'PUT' | 'HEAD';
+    limit?: number
+    bundle?: boolean
+    formData?: boolean
+    headers?: any
+    metaFields?: string[]
+    fieldName?: string
+    timeout?: number
+    responseUrlFieldName?: string
+    endpoint: string
+    method?: 'GET' | 'POST' | 'PUT' | 'HEAD' | 'get' | 'post' | 'put' | 'head'
+    locale?: XHRUploadLocale
   }
 }
 
-declare class XHRUpload extends Uppy.Plugin {
-  constructor(uppy: Uppy.Uppy, opts: XHRUpload.XHRUploadOptions);
-}
-
-export = XHRUpload;
+declare class XHRUpload extends Uppy.Plugin<XHRUpload.XHRUploadOptions> {}
 
-declare module '@uppy/core' {
-  export interface Uppy {
-    use(pluginClass: typeof XHRUpload, opts: XHRUpload.XHRUploadOptions): Uppy.Uppy;
-  }
-}
+export = XHRUpload

+ 19 - 0
packages/@uppy/xhr-upload/types/index.test-d.ts

@@ -0,0 +1,19 @@
+import Uppy = require('@uppy/core')
+import XHRUpload = require('../')
+
+Uppy<Uppy.StrictTypes>().use(XHRUpload, {
+  bundle: false,
+  formData: true,
+  endpoint: 'xyz'
+})
+
+function methodMayBeUpperOrLowerCase () {
+  Uppy<Uppy.StrictTypes>().use(XHRUpload, {
+    endpoint: '/upload',
+    method: 'post'
+  })
+  Uppy<Uppy.StrictTypes>().use(XHRUpload, {
+    endpoint: '/upload',
+    method: 'PUT'
+  })
+}

+ 0 - 9
packages/@uppy/xhr-upload/types/xhr-upload-tests.ts

@@ -1,9 +0,0 @@
-import Uppy = require('@uppy/core');
-import XHRUpload = require('../');
-
-{
-  Uppy().use(XHRUpload, {
-    bundle: false,
-    endpoint: 'xyz'
-  } as XHRUpload.XHRUploadOptions);
-}

+ 0 - 0
packages/uppy/types/uppy-tests.ts → packages/uppy/types/index.test-d.ts


+ 1 - 1
test/endtoend/typescript/main.ts

@@ -16,7 +16,7 @@ import {
 const isOnTravis = !!(process.env.TRAVIS && process.env.CI)
 const TUS_ENDPOINT = `http://${isOnTravis ? 'companion.test' : 'localhost'}:1080/files/`
 
-const uppy = Core({
+const uppy = Core<Core.StrictTypes>({
   debug: true,
   meta: {
     username: 'John',

+ 1 - 3
tsconfig.json

@@ -20,9 +20,7 @@
   },
   "include": [
     "packages/*/types/index.d.ts",
-    "packages/*/types/*-tests.ts",
-    "packages/@uppy/*/types/index.d.ts",
-    "packages/@uppy/*/types/*-tests.ts"
+    "packages/@uppy/*/types/index.d.ts"
   ],
   "exclude": [
     "packages/@uppy/companion"

+ 33 - 0
website/src/docs/uppy.md

@@ -30,6 +30,39 @@ In the [CDN package](/docs/#With-a-script-tag), it is available on the `Uppy` gl
 const Core = Uppy.Core
 ```
 
+## TypeScript
+
+When using TypeScript, Uppy has weak type checking by default. That means that the options to plugins are not type-checked. For example, this is allowed:
+```ts
+import Uppy = require('@uppy/core')
+import Tus = require('@uppy/tus')
+const uppy = Uppy()
+uppy.use(Tus, {
+  invalidOption: null,
+  endpoint: ['a', 'wrong', 'type']
+})
+```
+
+As of Uppy 1.10, Uppy supports a strict typechecking mode. This mode typechecks the options passed in to plugins. This will be the only mode in Uppy 2.0, but is currently optional to preserve backwards compatibility. The strict mode can be enabled by passing a special generic type parameter to the Uppy constructor:
+
+```ts
+import Uppy = require('@uppy/core')
+import Tus = require('@uppy/tus')
+const uppy = Uppy<Uppy.StrictTypes>()
+uppy.use(Tus, {
+  invalidOption: null // this will now make the compilation fail!
+})
+```
+
+If you are storing Uppy instances in your code, for example in a property on a React or Angular component class, make sure to add the `StrictTypes` flag there as well:
+```ts
+class MyComponent extends React.Component {
+  private uppy: Uppy<Uppy.StrictTypes>
+}
+```
+
+In Uppy 2.0, this generic parameter will be removed, and your plugin options will always be type-checked.
+
 ## Options
 
 The Uppy core module has the following configurable options:

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels