Просмотр исходного кода

@uppy/transloadit: migrate to TS (#4987)

Co-authored-by: Antoine du Hamel <antoine@transloadit.com>
Merlijn Vos 1 год назад
Родитель
Сommit
700944e25b

+ 3 - 2
packages/@uppy/core/src/Uppy.ts

@@ -269,13 +269,13 @@ type UploadCompleteCallback<M extends Meta, B extends Body> = (
   result: UploadResult<M, B>,
 ) => void
 type ErrorCallback<M extends Meta, B extends Body> = (
-  error: { message?: string; details?: string },
+  error: { name: string; message: string; details?: string },
   file?: UppyFile<M, B>,
   response?: UppyFile<M, B>['response'],
 ) => void
 type UploadErrorCallback<M extends Meta, B extends Body> = (
   file: UppyFile<M, B> | undefined,
-  error: { message: string; details?: string },
+  error: { name: string; message: string; details?: string },
   response?:
     | Omit<NonNullable<UppyFile<M, B>['response']>, 'uploadURL'>
     | undefined,
@@ -789,6 +789,7 @@ export class Uppy<M extends Meta, B extends Body> {
 
   #informAndEmit(
     errors: {
+      name: string
       message: string
       isUserFacing?: boolean
       details?: string

+ 1 - 0
packages/@uppy/transloadit/.npmignore

@@ -0,0 +1 @@
+tsconfig.*

+ 67 - 60
packages/@uppy/transloadit/src/Assembly.test.js

@@ -1,10 +1,10 @@
 import { describe, expect, it, vi } from 'vitest'
 import { RateLimitedQueue } from '@uppy/utils/lib/RateLimitedQueue'
-import Assembly from './Assembly.js'
+import Assembly from './Assembly.ts'
 
 describe('Transloadit/Assembly', () => {
   describe('status diffing', () => {
-    function attemptDiff (prev, next) {
+    function attemptDiff(prev, next) {
       const assembly = new Assembly(prev, new RateLimitedQueue())
       const events = []
       assembly.emit = vi.fn((name, ...args) => {
@@ -17,43 +17,52 @@ describe('Transloadit/Assembly', () => {
     }
 
     it('ASSEMBLY_UPLOADING → ASSEMBLY_EXECUTING', () => {
-      const result = attemptDiff({
-        ok: 'ASSEMBLY_UPLOADING',
-        uploads: {},
-        results: {},
-      }, {
-        ok: 'ASSEMBLY_EXECUTING',
-        uploads: {},
-        results: {},
-      })
+      const result = attemptDiff(
+        {
+          ok: 'ASSEMBLY_UPLOADING',
+          uploads: {},
+          results: {},
+        },
+        {
+          ok: 'ASSEMBLY_EXECUTING',
+          uploads: {},
+          results: {},
+        },
+      )
 
       expect(result[0]).toEqual(['executing'])
     })
 
     it('ASSEMBLY_EXECUTING → ASSEMBLY_COMPLETED', () => {
-      const result = attemptDiff({
-        ok: 'ASSEMBLY_EXECUTING',
-        uploads: {},
-        results: {},
-      }, {
-        ok: 'ASSEMBLY_COMPLETED',
-        uploads: {},
-        results: {},
-      })
+      const result = attemptDiff(
+        {
+          ok: 'ASSEMBLY_EXECUTING',
+          uploads: {},
+          results: {},
+        },
+        {
+          ok: 'ASSEMBLY_COMPLETED',
+          uploads: {},
+          results: {},
+        },
+      )
 
       expect(result[0]).toEqual(['finished'])
     })
 
     it('ASSEMBLY_UPLOADING → ASSEMBLY_COMPLETED', () => {
-      const result = attemptDiff({
-        ok: 'ASSEMBLY_UPLOADING',
-        uploads: {},
-        results: {},
-      }, {
-        ok: 'ASSEMBLY_COMPLETED',
-        uploads: {},
-        results: {},
-      })
+      const result = attemptDiff(
+        {
+          ok: 'ASSEMBLY_UPLOADING',
+          uploads: {},
+          results: {},
+        },
+        {
+          ok: 'ASSEMBLY_COMPLETED',
+          uploads: {},
+          results: {},
+        },
+      )
 
       expect(result[0]).toEqual(['executing'])
       expect(result[1]).toEqual(['metadata'])
@@ -61,33 +70,39 @@ describe('Transloadit/Assembly', () => {
     })
 
     it('emits events for new files', () => {
-      const result = attemptDiff({
-        ok: 'ASSEMBLY_UPLOADING',
-        uploads: {},
-        results: {},
-      }, {
-        ok: 'ASSEMBLY_UPLOADING',
-        uploads: {
-          some_id: { id: 'some_id' },
+      const result = attemptDiff(
+        {
+          ok: 'ASSEMBLY_UPLOADING',
+          uploads: {},
+          results: {},
         },
-        results: {},
-      })
+        {
+          ok: 'ASSEMBLY_UPLOADING',
+          uploads: {
+            some_id: { id: 'some_id' },
+          },
+          results: {},
+        },
+      )
 
       expect(result[0]).toEqual(['upload', { id: 'some_id' }])
     })
 
     it('emits executing, then upload, on new files + status change', () => {
-      const result = attemptDiff({
-        ok: 'ASSEMBLY_UPLOADING',
-        uploads: {},
-        results: {},
-      }, {
-        ok: 'ASSEMBLY_EXECUTING',
-        uploads: {
-          some_id: { id: 'some_id' },
+      const result = attemptDiff(
+        {
+          ok: 'ASSEMBLY_UPLOADING',
+          uploads: {},
+          results: {},
         },
-        results: {},
-      })
+        {
+          ok: 'ASSEMBLY_EXECUTING',
+          uploads: {
+            some_id: { id: 'some_id' },
+          },
+          results: {},
+        },
+      )
 
       expect(result[0]).toEqual(['executing'])
       expect(result[1]).toEqual(['upload', { id: 'some_id' }])
@@ -108,11 +123,7 @@ describe('Transloadit/Assembly', () => {
           cool_video: { id: 'cool_video' },
         },
         results: {
-          step_one: [
-            { id: 'thumb1' },
-            { id: 'thumb2' },
-            { id: 'thumb3' },
-          ],
+          step_one: [{ id: 'thumb1' }, { id: 'thumb2' }, { id: 'thumb3' }],
         },
       }
       const three = {
@@ -127,9 +138,7 @@ describe('Transloadit/Assembly', () => {
             { id: 'thumb3' },
             { id: 'thumb4' },
           ],
-          step_two: [
-            { id: 'transcript' },
-          ],
+          step_two: [{ id: 'transcript' }],
         },
       }
 
@@ -162,9 +171,7 @@ describe('Transloadit/Assembly', () => {
             { id: 'thumb3' },
             { id: 'thumb4' },
           ],
-          step_two: [
-            { id: 'transcript' },
-          ],
+          step_two: [{ id: 'transcript' }],
         },
       }
 

+ 63 - 43
packages/@uppy/transloadit/src/Assembly.js → packages/@uppy/transloadit/src/Assembly.ts

@@ -2,16 +2,19 @@ import Emitter from 'component-emitter'
 import has from '@uppy/utils/lib/hasProperty'
 import NetworkError from '@uppy/utils/lib/NetworkError'
 import fetchWithNetworkError from '@uppy/utils/lib/fetchWithNetworkError'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore untyped
+import type {
+  RateLimitedQueue,
+  WrapPromiseFunctionType,
+} from '@uppy/utils/lib/RateLimitedQueue'
+import type { AssemblyResponse } from '.'
 
 const ASSEMBLY_UPLOADING = 'ASSEMBLY_UPLOADING'
 const ASSEMBLY_EXECUTING = 'ASSEMBLY_EXECUTING'
 const ASSEMBLY_COMPLETED = 'ASSEMBLY_COMPLETED'
 
-const statusOrder = [
-  ASSEMBLY_UPLOADING,
-  ASSEMBLY_EXECUTING,
-  ASSEMBLY_COMPLETED,
-]
+const statusOrder = [ASSEMBLY_UPLOADING, ASSEMBLY_EXECUTING, ASSEMBLY_COMPLETED]
 
 /**
  * Check that an assembly status is equal to or larger than some desired status.
@@ -23,20 +26,26 @@ const statusOrder = [
  * …so that we can emit the 'executing' event even if the execution step was so
  * fast that we missed it.
  */
-function isStatus (status, test) {
+function isStatus(status: string, test: string) {
   return statusOrder.indexOf(status) >= statusOrder.indexOf(test)
 }
 
 class TransloaditAssembly extends Emitter {
-  #rateLimitedQueue
+  #rateLimitedQueue: RateLimitedQueue
 
-  #fetchWithNetworkError
+  #fetchWithNetworkError: WrapPromiseFunctionType<typeof fetchWithNetworkError>
 
   #previousFetchStatusStillPending = false
 
-  #sse
+  #sse: EventSource | null
 
-  constructor (assembly, rateLimitedQueue) {
+  status: AssemblyResponse
+
+  pollInterval: ReturnType<typeof setInterval> | null
+
+  closed: boolean
+
+  constructor(assembly: AssemblyResponse, rateLimitedQueue: RateLimitedQueue) {
     super()
 
     // The current assembly status.
@@ -47,29 +56,28 @@ class TransloaditAssembly extends Emitter {
     this.closed = false
 
     this.#rateLimitedQueue = rateLimitedQueue
-    this.#fetchWithNetworkError = rateLimitedQueue.wrapPromiseFunction(fetchWithNetworkError)
+    this.#fetchWithNetworkError = rateLimitedQueue.wrapPromiseFunction(
+      fetchWithNetworkError,
+    )
   }
 
-  connect () {
+  connect(): void {
     this.#connectServerSentEvents()
     this.#beginPolling()
   }
 
-  #onFinished () {
+  #onFinished() {
     this.emit('finished')
     this.close()
   }
 
-  #connectServerSentEvents () {
-    this.#sse = new EventSource(`${this.status.websocket_url}?assembly=${this.status.assembly_id}`)
+  #connectServerSentEvents() {
+    this.#sse = new EventSource(
+      `${this.status.websocket_url}?assembly=${this.status.assembly_id}`,
+    )
 
     this.#sse.addEventListener('open', () => {
-      // if server side events works, we don't need websockets anymore (it's just a fallback)
-      if (this.socket) {
-        this.socket.disconnect()
-        this.socket = null
-      }
-      clearInterval(this.pollInterval)
+      clearInterval(this.pollInterval!)
       this.pollInterval = null
     })
 
@@ -115,15 +123,18 @@ class TransloaditAssembly extends Emitter {
       try {
         this.#onError(JSON.parse(e.data))
       } catch {
-        this.#onError({ msg: e.data })
+        this.#onError(new Error(e.data))
       }
       // Refetch for updated status code
       this.#fetchStatus({ diff: false })
     })
   }
 
-  #onError (status) {
-    this.emit('error', Object.assign(new Error(status.msg), status))
+  #onError(assemblyOrError: AssemblyResponse | NetworkError | Error) {
+    this.emit(
+      'error',
+      Object.assign(new Error(assemblyOrError.message), assemblyOrError),
+    )
     this.close()
   }
 
@@ -133,7 +144,7 @@ class TransloaditAssembly extends Emitter {
    * If the SSE connection fails or takes a long time, we won't miss any
    * events.
    */
-  #beginPolling () {
+  #beginPolling() {
     this.pollInterval = setInterval(() => {
       this.#fetchStatus()
     }, 2000)
@@ -145,12 +156,19 @@ class TransloaditAssembly extends Emitter {
    * Pass `diff: false` to avoid emitting diff events, instead only emitting
    * 'status'.
    */
-  async #fetchStatus ({ diff = true } = {}) {
-    if (this.closed || this.#rateLimitedQueue.isPaused || this.#previousFetchStatusStillPending) return
+  async #fetchStatus({ diff = true } = {}) {
+    if (
+      this.closed ||
+      this.#rateLimitedQueue.isPaused ||
+      this.#previousFetchStatusStillPending
+    )
+      return
 
     try {
       this.#previousFetchStatusStillPending = true
-      const response = await this.#fetchWithNetworkError(this.status.assembly_ssl_url)
+      const response = await this.#fetchWithNetworkError(
+        this.status.assembly_ssl_url,
+      )
       this.#previousFetchStatusStillPending = false
 
       if (this.closed) return
@@ -166,6 +184,7 @@ class TransloaditAssembly extends Emitter {
       }
 
       const status = await response.json()
+
       // Avoid updating if we closed during this request's lifetime.
       if (this.closed) return
       this.emit('status', status)
@@ -180,17 +199,15 @@ class TransloaditAssembly extends Emitter {
     }
   }
 
-  update () {
+  update(): Promise<void> {
     return this.#fetchStatus({ diff: true })
   }
 
   /**
    * Update this assembly's status with a full new object. Events will be
    * emitted for status changes, new files, and new results.
-   *
-   * @param {object} next The new assembly status object.
    */
-  updateStatus (next) {
+  updateStatus(next: AssemblyResponse): void {
     this.#diffStatus(this.status, next)
     this.status = next
   }
@@ -198,11 +215,8 @@ class TransloaditAssembly extends Emitter {
   /**
    * Diff two assembly statuses, and emit the events necessary to go from `prev`
    * to `next`.
-   *
-   * @param {object} prev The previous assembly status.
-   * @param {object} next The new assembly status.
    */
-  #diffStatus (prev, next) {
+  #diffStatus(prev: AssemblyResponse, next: AssemblyResponse) {
     const prevStatus = prev.ok
     const nextStatus = next.ok
 
@@ -219,8 +233,9 @@ class TransloaditAssembly extends Emitter {
     // The below checks run in this order, that way even if we jump from
     // UPLOADING straight to FINISHED all the events are emitted as expected.
 
-    const nowExecuting = isStatus(nextStatus, ASSEMBLY_EXECUTING)
-      && !isStatus(prevStatus, ASSEMBLY_EXECUTING)
+    const nowExecuting =
+      isStatus(nextStatus, ASSEMBLY_EXECUTING) &&
+      !isStatus(prevStatus, ASSEMBLY_EXECUTING)
     if (nowExecuting) {
       // Without SSE, this is our only way to tell if uploading finished.
       // Hence, we emit this just before the 'upload's and before the 'metadata'
@@ -229,10 +244,13 @@ class TransloaditAssembly extends Emitter {
       this.emit('executing')
     }
 
-    // Find new uploaded files.
+    // Only emit if the upload is new (not in prev.uploads).
     Object.keys(next.uploads)
       .filter((upload) => !has(prev.uploads, upload))
       .forEach((upload) => {
+        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+        // @ts-ignore either the types are wrong or the tests are wrong.
+        // types think next.uploads is an array, but the tests pass an object.
         this.emit('upload', next.uploads[upload])
       })
 
@@ -252,8 +270,10 @@ class TransloaditAssembly extends Emitter {
         })
     })
 
-    if (isStatus(nextStatus, ASSEMBLY_COMPLETED)
-        && !isStatus(prevStatus, ASSEMBLY_COMPLETED)) {
+    if (
+      isStatus(nextStatus, ASSEMBLY_COMPLETED) &&
+      !isStatus(prevStatus, ASSEMBLY_COMPLETED)
+    ) {
       this.emit('finished')
     }
 
@@ -263,13 +283,13 @@ class TransloaditAssembly extends Emitter {
   /**
    * Stop updating this assembly.
    */
-  close () {
+  close(): void {
     this.closed = true
     if (this.#sse) {
       this.#sse.close()
       this.#sse = null
     }
-    clearInterval(this.pollInterval)
+    clearInterval(this.pollInterval!)
     this.pollInterval = null
   }
 }

+ 57 - 47
packages/@uppy/transloadit/src/AssemblyOptions.test.js

@@ -1,11 +1,9 @@
 import { describe, expect, it } from 'vitest'
-import AssemblyOptions from './AssemblyOptions.js'
+import AssemblyOptions from './AssemblyOptions.ts'
 
 describe('Transloadit/AssemblyOptions', () => {
   it('Validates response from assemblyOptions()', async () => {
-    const options = new AssemblyOptions([
-      { name: 'testfile' },
-    ], {
+    const options = new AssemblyOptions([{ name: 'testfile' }], {
       assemblyOptions: (file) => {
         expect(file.name).toBe('testfile')
         return {
@@ -23,21 +21,24 @@ describe('Transloadit/AssemblyOptions', () => {
     const data = new Uint8Array(10)
     data.size = data.byteLength
 
-    const options = new AssemblyOptions([
-      { name: 'a.png', data },
-      { name: 'b.png', data },
-      { name: 'c.png', data },
-      { name: 'd.png', data },
-    ], {
-      assemblyOptions: (file) => ({
-        params: {
-          auth: { key: 'fake key' },
-          steps: {
-            fake_step: { data: file.name },
+    const options = new AssemblyOptions(
+      [
+        { name: 'a.png', data },
+        { name: 'b.png', data },
+        { name: 'c.png', data },
+        { name: 'd.png', data },
+      ],
+      {
+        assemblyOptions: (file) => ({
+          params: {
+            auth: { key: 'fake key' },
+            steps: {
+              fake_step: { data: file.name },
+            },
           },
-        },
-      }),
-    })
+        }),
+      },
+    )
 
     const assemblies = await options.build()
     expect(assemblies).toHaveLength(4)
@@ -51,21 +52,24 @@ describe('Transloadit/AssemblyOptions', () => {
     const data = new Uint8Array(10)
     const data2 = new Uint8Array(20)
 
-    const options = new AssemblyOptions([
-      { name: 'a.png', data, size: data.byteLength },
-      { name: 'b.png', data, size: data.byteLength },
-      { name: 'c.png', data, size: data.byteLength },
-      { name: 'd.png', data: data2, size: data2.byteLength },
-    ], {
-      assemblyOptions: (file) => ({
-        params: {
-          auth: { key: 'fake key' },
-          steps: {
-            fake_step: { data: file.size },
+    const options = new AssemblyOptions(
+      [
+        { name: 'a.png', data, size: data.byteLength },
+        { name: 'b.png', data, size: data.byteLength },
+        { name: 'c.png', data, size: data.byteLength },
+        { name: 'd.png', data: data2, size: data2.byteLength },
+      ],
+      {
+        assemblyOptions: (file) => ({
+          params: {
+            auth: { key: 'fake key' },
+            steps: {
+              fake_step: { data: file.size },
+            },
           },
-        },
-      }),
-    })
+        }),
+      },
+    )
 
     const assemblies = await options.build()
     expect(assemblies).toHaveLength(2)
@@ -77,7 +81,7 @@ describe('Transloadit/AssemblyOptions', () => {
 
   it('Does not create an Assembly if no files are being uploaded', async () => {
     const options = new AssemblyOptions([], {
-      assemblyOptions () {
+      assemblyOptions() {
         throw new Error('should not create Assembly')
       },
     })
@@ -88,7 +92,7 @@ describe('Transloadit/AssemblyOptions', () => {
   it('Creates an Assembly if no files are being uploaded but `alwaysRunAssembly` is enabled', async () => {
     const options = new AssemblyOptions([], {
       alwaysRunAssembly: true,
-      async assemblyOptions (file) {
+      async assemblyOptions(file) {
         expect(file).toBe(null)
         return {
           params: {
@@ -103,7 +107,7 @@ describe('Transloadit/AssemblyOptions', () => {
   })
 
   it('Collects metadata if `fields` is an array', async () => {
-    function defaultGetAssemblyOptions (file, options) {
+    function defaultGetAssemblyOptions(file, options) {
       return {
         params: options.params,
         signature: options.signature,
@@ -111,19 +115,25 @@ describe('Transloadit/AssemblyOptions', () => {
       }
     }
 
-    const options = new AssemblyOptions([{
-      id: 1,
-      meta: { watermark: 'Some text' },
-    }, {
-      id: 2,
-      meta: { watermark: 'ⓒ Transloadit GmbH' },
-    }], {
-      fields: ['watermark'],
-      params: {
-        auth: { key: 'fake key' },
+    const options = new AssemblyOptions(
+      [
+        {
+          id: 1,
+          meta: { watermark: 'Some text' },
+        },
+        {
+          id: 2,
+          meta: { watermark: 'ⓒ Transloadit GmbH' },
+        },
+      ],
+      {
+        fields: ['watermark'],
+        params: {
+          auth: { key: 'fake key' },
+        },
+        assemblyOptions: defaultGetAssemblyOptions,
       },
-      assemblyOptions: defaultGetAssemblyOptions,
-    })
+    )
 
     const assemblies = await options.build()
     expect(assemblies).toHaveLength(2)

+ 51 - 18
packages/@uppy/transloadit/src/AssemblyOptions.js → packages/@uppy/transloadit/src/AssemblyOptions.ts

@@ -1,9 +1,11 @@
 import ErrorWithCause from '@uppy/utils/lib/ErrorWithCause'
+import type { Body, Meta, UppyFile } from '@uppy/utils/lib/UppyFile'
+import type { AssemblyParameters, Opts, AssemblyOptions as Options } from '.'
 
 /**
  * Check that Assembly parameters are present and include all required fields.
  */
-function validateParams (params) {
+function validateParams(params?: AssemblyParameters | null): void {
   if (params == null) {
     throw new Error('Transloadit: The `params` option is required.')
   }
@@ -14,23 +16,41 @@ function validateParams (params) {
       params = JSON.parse(params)
     } catch (err) {
       // Tell the user that this is not an Uppy bug!
-      throw new ErrorWithCause('Transloadit: The `params` option is a malformed JSON string.', { cause: err })
+      throw new ErrorWithCause(
+        'Transloadit: The `params` option is a malformed JSON string.',
+        { cause: err },
+      )
     }
   }
 
-  if (!params.auth || !params.auth.key) {
-    throw new Error('Transloadit: The `params.auth.key` option is required. '
-      + 'You can find your Transloadit API key at https://transloadit.com/c/template-credentials')
+  if (!params!.auth || !params!.auth.key) {
+    throw new Error(
+      'Transloadit: The `params.auth.key` option is required. ' +
+        'You can find your Transloadit API key at https://transloadit.com/c/template-credentials',
+    )
   }
 }
+export type OptionsWithRestructuredFields = Omit<Options, 'fields'> & {
+  fields: Record<string, string | number>
+}
 
 /**
  * Combine Assemblies with the same options into a single Assembly for all the
  * relevant files.
  */
-function dedupe (list) {
-  const dedupeMap = Object.create(null)
-  for (const { fileIDs, options } of list.filter(Boolean)) {
+function dedupe(
+  list: Array<
+    { fileIDs: string[]; options: OptionsWithRestructuredFields } | undefined
+  >,
+) {
+  const dedupeMap: Record<
+    string,
+    { fileIDArrays: string[][]; options: OptionsWithRestructuredFields }
+  > = Object.create(null)
+  for (const { fileIDs, options } of list.filter(Boolean) as Array<{
+    fileIDs: string[]
+    options: OptionsWithRestructuredFields
+  }>) {
     const id = JSON.stringify(options)
     if (id in dedupeMap) {
       dedupeMap[id].fileIDArrays.push(fileIDs)
@@ -48,18 +68,25 @@ function dedupe (list) {
   }))
 }
 
-async function getAssemblyOptions (file, options) {
-  const assemblyOptions = typeof options.assemblyOptions === 'function'
-    ? await options.assemblyOptions(file, options)
-    : options.assemblyOptions
+async function getAssemblyOptions<M extends Meta, B extends Body>(
+  file: UppyFile<M, B> | null,
+  options: Opts<M, B>,
+): Promise<OptionsWithRestructuredFields> {
+  const assemblyOptions = (
+    typeof options.assemblyOptions === 'function' ?
+      await options.assemblyOptions(file, options)
+    : options.assemblyOptions) as OptionsWithRestructuredFields
 
   validateParams(assemblyOptions.params)
 
   const { fields } = assemblyOptions
   if (Array.isArray(fields)) {
-    assemblyOptions.fields = file == null ? {} : Object.fromEntries(
-      fields.map((fieldName) => [fieldName, file.meta[fieldName]]),
-    )
+    assemblyOptions.fields =
+      file == null ?
+        {}
+      : Object.fromEntries(
+          fields.map((fieldName) => [fieldName, file.meta[fieldName]]),
+        )
   } else if (fields == null) {
     assemblyOptions.fields = {}
   }
@@ -71,8 +98,12 @@ async function getAssemblyOptions (file, options) {
  * Turn Transloadit plugin options and a list of files into a list of Assembly
  * options.
  */
-class AssemblyOptions {
-  constructor (files, opts) {
+class AssemblyOptions<M extends Meta, B extends Body> {
+  opts: Opts<M, B>
+
+  files: UppyFile<M, B>[]
+
+  constructor(files: UppyFile<M, B>[], opts: Opts<M, B>) {
     this.files = files
     this.opts = opts
   }
@@ -83,7 +114,9 @@ class AssemblyOptions {
    *  - fileIDs - an array of file IDs to add to this Assembly
    *  - options - Assembly options
    */
-  async build () {
+  async build(): Promise<
+    { fileIDs: string[]; options: OptionsWithRestructuredFields }[]
+  > {
     const options = this.opts
 
     if (this.files.length > 0) {

+ 32 - 16
packages/@uppy/transloadit/src/AssemblyWatcher.js → packages/@uppy/transloadit/src/AssemblyWatcher.ts

@@ -1,4 +1,7 @@
+import type { Uppy } from '@uppy/core'
+import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
 import Emitter from 'component-emitter'
+import type { AssemblyResponse } from '.'
 
 /**
  * Track completion of multiple assemblies.
@@ -8,25 +11,30 @@ import Emitter from 'component-emitter'
  * Exposes a `.promise` property that resolves when all assemblies have
  * completed (or failed).
  */
-class TransloaditAssemblyWatcher extends Emitter {
+class TransloaditAssemblyWatcher<
+  M extends Meta,
+  B extends Body,
+> extends Emitter {
   #assemblyIDs
 
-  #reject
+  #remaining: number
 
-  #remaining
+  promise: Promise<void>
 
-  #resolve
+  #resolve: () => void
+
+  #reject: (reason?: string) => void
 
   #uppy
 
-  constructor (uppy, assemblyIDs) {
+  constructor(uppy: Uppy<M, B>, assemblyIDs: string[]) {
     super()
 
     this.#uppy = uppy
     this.#assemblyIDs = assemblyIDs
     this.#remaining = assemblyIDs.length
 
-    this.promise = new Promise((resolve, reject) => {
+    this.promise = new Promise<void>((resolve, reject) => {
       this.#resolve = resolve
       this.#reject = reject
     })
@@ -37,23 +45,25 @@ class TransloaditAssemblyWatcher extends Emitter {
   /**
    * Are we watching this assembly ID?
    */
-  #watching (id) {
+  #watching(id: string) {
     return this.#assemblyIDs.indexOf(id) !== -1
   }
 
-  #onAssemblyComplete = (assembly) => {
+  #onAssemblyComplete = (assembly: AssemblyResponse) => {
     if (!this.#watching(assembly.assembly_id)) {
       return
     }
 
-    this.#uppy.log(`[Transloadit] AssemblyWatcher: Got Assembly finish ${assembly.assembly_id}`)
+    this.#uppy.log(
+      `[Transloadit] AssemblyWatcher: Got Assembly finish ${assembly.assembly_id}`,
+    )
 
     this.emit('assembly-complete', assembly.assembly_id)
 
     this.#checkAllComplete()
   }
 
-  #onAssemblyCancel = (assembly) => {
+  #onAssemblyCancel = (assembly: AssemblyResponse) => {
     if (!this.#watching(assembly.assembly_id)) {
       return
     }
@@ -61,12 +71,14 @@ class TransloaditAssemblyWatcher extends Emitter {
     this.#checkAllComplete()
   }
 
-  #onAssemblyError = (assembly, error) => {
+  #onAssemblyError = (assembly: AssemblyResponse, error: Error) => {
     if (!this.#watching(assembly.assembly_id)) {
       return
     }
 
-    this.#uppy.log(`[Transloadit] AssemblyWatcher: Got Assembly error ${assembly.assembly_id}`)
+    this.#uppy.log(
+      `[Transloadit] AssemblyWatcher: Got Assembly error ${assembly.assembly_id}`,
+    )
     this.#uppy.log(error)
 
     this.emit('assembly-error', assembly.assembly_id, error)
@@ -74,7 +86,11 @@ class TransloaditAssemblyWatcher extends Emitter {
     this.#checkAllComplete()
   }
 
-  #onImportError = (assembly, fileID, error) => {
+  #onImportError = (
+    assembly: AssemblyResponse,
+    fileID: string,
+    error: Error,
+  ) => {
     if (!this.#watching(assembly.assembly_id)) {
       return
     }
@@ -87,7 +103,7 @@ class TransloaditAssemblyWatcher extends Emitter {
     this.#onAssemblyError(assembly, error)
   }
 
-  #checkAllComplete () {
+  #checkAllComplete() {
     this.#remaining -= 1
     if (this.#remaining === 0) {
       // We're done, these listeners can be removed
@@ -96,14 +112,14 @@ class TransloaditAssemblyWatcher extends Emitter {
     }
   }
 
-  #removeListeners () {
+  #removeListeners() {
     this.#uppy.off('transloadit:complete', this.#onAssemblyComplete)
     this.#uppy.off('transloadit:assembly-cancel', this.#onAssemblyCancel)
     this.#uppy.off('transloadit:assembly-error', this.#onAssemblyError)
     this.#uppy.off('transloadit:import-error', this.#onImportError)
   }
 
-  #addListeners () {
+  #addListeners() {
     this.#uppy.on('transloadit:complete', this.#onAssemblyComplete)
     this.#uppy.on('transloadit:assembly-cancel', this.#onAssemblyCancel)
     this.#uppy.on('transloadit:assembly-error', this.#onAssemblyError)

+ 0 - 213
packages/@uppy/transloadit/src/Client.js

@@ -1,213 +0,0 @@
-import fetchWithNetworkError from '@uppy/utils/lib/fetchWithNetworkError'
-
-const ASSEMBLIES_ENDPOINT = '/assemblies'
-
-/**
- * A Barebones HTTP API client for Transloadit.
- */
-export default class Client {
-  #headers = {}
-
-  #fetchWithNetworkError
-
-  constructor (opts = {}) {
-    this.opts = opts
-
-    if (this.opts.client != null) {
-      this.#headers['Transloadit-Client'] = this.opts.client
-    }
-
-    this.#fetchWithNetworkError = this.opts.rateLimitedQueue.wrapPromiseFunction(fetchWithNetworkError)
-  }
-
-  /**
-   * @param  {[RequestInfo | URL, RequestInit]} args
-   * @returns {Promise<any>}
-   */
-  #fetchJSON (...args) {
-    return this.#fetchWithNetworkError(...args).then(response => {
-      if (response.status === 429) {
-        this.opts.rateLimitedQueue.rateLimit(2_000)
-        return this.#fetchJSON(...args)
-      }
-
-      if (!response.ok) {
-        const serverError = new Error(response.statusText)
-        serverError.statusCode = response.status
-
-        if (!`${args[0]}`.endsWith(ASSEMBLIES_ENDPOINT)) return Promise.reject(serverError)
-
-        // Failed assembly requests should return a more detailed error in JSON.
-        return response.json().then(assembly => {
-          if (!assembly.error) throw serverError
-
-          const error = new Error(assembly.error)
-          error.details = assembly.message
-          error.assembly = assembly
-          if (assembly.assembly_id) {
-            error.details += ` Assembly ID: ${assembly.assembly_id}`
-          }
-          throw error
-        }, err => {
-          // eslint-disable-next-line no-param-reassign
-          err.cause = serverError
-          throw err
-        })
-      }
-
-      return response.json()
-    })
-  }
-
-  /**
-   * Create a new assembly.
-   *
-   * @param {object} options
-   * @param {string|object} options.params
-   * @param {object} options.fields
-   * @param {string} options.signature
-   * @param {number} options.expectedFiles
-   */
-  createAssembly ({
-    params,
-    fields,
-    signature,
-    expectedFiles,
-  }) {
-    const data = new FormData()
-    data.append('params', typeof params === 'string'
-      ? params
-      : JSON.stringify(params))
-    if (signature) {
-      data.append('signature', signature)
-    }
-
-    Object.keys(fields).forEach((key) => {
-      data.append(key, fields[key])
-    })
-    data.append('num_expected_upload_files', expectedFiles)
-
-    const url = new URL(ASSEMBLIES_ENDPOINT, `${this.opts.service}`).href
-    return this.#fetchJSON(url, {
-      method: 'POST',
-      headers: this.#headers,
-      body: data,
-    })
-      .catch((err) => this.#reportError(err, { url, type: 'API_ERROR' }))
-  }
-
-  /**
-   * Reserve resources for a file in an Assembly. Then addFile can be used later.
-   *
-   * @param {object} assembly
-   * @param {UppyFile} file
-   */
-  reserveFile (assembly, file) {
-    const size = encodeURIComponent(file.size)
-    const url = `${assembly.assembly_ssl_url}/reserve_file?size=${size}`
-    return this.#fetchJSON(url, { method: 'POST', headers: this.#headers })
-      .catch((err) => this.#reportError(err, { assembly, file, url, type: 'API_ERROR' }))
-  }
-
-  /**
-   * Import a remote file to an Assembly.
-   *
-   * @param {object} assembly
-   * @param {UppyFile} file
-   */
-  addFile (assembly, file) {
-    if (!file.uploadURL) {
-      return Promise.reject(new Error('File does not have an `uploadURL`.'))
-    }
-    const size = encodeURIComponent(file.size)
-    const uploadUrl = encodeURIComponent(file.uploadURL)
-    const filename = encodeURIComponent(file.name)
-    const fieldname = 'file'
-
-    const qs = `size=${size}&filename=${filename}&fieldname=${fieldname}&s3Url=${uploadUrl}`
-    const url = `${assembly.assembly_ssl_url}/add_file?${qs}`
-    return this.#fetchJSON(url, { method: 'POST', headers: this.#headers })
-      .catch((err) => this.#reportError(err, { assembly, file, url, type: 'API_ERROR' }))
-  }
-
-  /**
-   * Update the number of expected files in an already created assembly.
-   *
-   * @param {object} assembly
-   * @param {number} num_expected_upload_files
-   */
-  updateNumberOfFilesInAssembly (assembly, num_expected_upload_files) {
-    const url = new URL(assembly.assembly_ssl_url)
-    url.pathname = '/update_assemblies'
-    const body = JSON.stringify({
-      assembly_updates: [{
-        assembly_id: assembly.assembly_id,
-        num_expected_upload_files,
-      }],
-    })
-    return this.#fetchJSON(url, { method: 'POST', headers: this.#headers, body })
-      .catch((err) => this.#reportError(err, { url, type: 'API_ERROR' }))
-  }
-
-  /**
-   * Cancel a running Assembly.
-   *
-   * @param {object} assembly
-   */
-  cancelAssembly (assembly) {
-    const url = assembly.assembly_ssl_url
-    return this.#fetchJSON(url, { method: 'DELETE', headers: this.#headers })
-      .catch((err) => this.#reportError(err, { url, type: 'API_ERROR' }))
-  }
-
-  /**
-   * Get the current status for an assembly.
-   *
-   * @param {string} url The status endpoint of the assembly.
-   */
-  getAssemblyStatus (url) {
-    return this.#fetchJSON(url, { headers: this.#headers })
-      .catch((err) => this.#reportError(err, { url, type: 'STATUS_ERROR' }))
-  }
-
-  submitError (err, { endpoint, instance, assembly } = {}) {
-    const message = err.details
-      ? `${err.message} (${err.details})`
-      : err.message
-
-    return this.#fetchJSON('https://transloaditstatus.com/client_error', {
-      method: 'POST',
-      body: JSON.stringify({
-        endpoint,
-        instance,
-        assembly_id: assembly,
-        agent: typeof navigator !== 'undefined' ? navigator.userAgent : '',
-        client: this.opts.client,
-        error: message,
-      }),
-    })
-  }
-
-  #reportError = (err, params) => {
-    if (this.opts.errorReporting === false) {
-      throw err
-    }
-
-    const opts = {
-      type: params.type,
-    }
-    if (params.assembly) {
-      opts.assembly = params.assembly.assembly_id
-      opts.instance = params.assembly.instance
-    }
-    if (params.url) {
-      opts.endpoint = params.url
-    }
-
-    this.submitError(err, opts).catch(() => {
-      // not much we can do then is there
-    })
-
-    throw err
-  }
-}

+ 280 - 0
packages/@uppy/transloadit/src/Client.ts

@@ -0,0 +1,280 @@
+import type {
+  RateLimitedQueue,
+  WrapPromiseFunctionType,
+} from '@uppy/utils/lib/RateLimitedQueue'
+import type { Body, Meta, UppyFile } from '@uppy/utils/lib/UppyFile'
+import fetchWithNetworkError from '@uppy/utils/lib/fetchWithNetworkError'
+import type { AssemblyResponse } from '.'
+import type { OptionsWithRestructuredFields } from './AssemblyOptions'
+
+const ASSEMBLIES_ENDPOINT = '/assemblies'
+
+type Opts = {
+  client?: string
+  service: string
+  rateLimitedQueue: RateLimitedQueue
+  errorReporting: boolean
+}
+
+export class AssemblyError extends Error {
+  details: string | undefined
+
+  assembly: AssemblyResponse
+
+  constructor(
+    message: string,
+    details: string | undefined,
+    assembly: AssemblyResponse,
+  ) {
+    super(message)
+    this.details = details
+    this.assembly = assembly
+  }
+}
+
+/**
+ * A Barebones HTTP API client for Transloadit.
+ */
+export default class Client<M extends Meta, B extends Body> {
+  #headers: Record<string, string> = {}
+
+  #fetchWithNetworkError: WrapPromiseFunctionType<typeof fetchWithNetworkError>
+
+  opts: Opts
+
+  constructor(opts: Opts) {
+    this.opts = opts
+
+    if (this.opts.client != null) {
+      this.#headers['Transloadit-Client'] = this.opts.client
+    }
+
+    this.#fetchWithNetworkError =
+      this.opts.rateLimitedQueue.wrapPromiseFunction(fetchWithNetworkError)
+  }
+
+  async #fetchJSON(
+    ...args: Parameters<typeof fetchWithNetworkError>
+  ): Promise<AssemblyResponse> {
+    const response = await this.#fetchWithNetworkError(...args)
+
+    if (response.status === 429) {
+      this.opts.rateLimitedQueue.rateLimit(2_000)
+      return this.#fetchJSON(...args)
+    }
+
+    if (!response.ok) {
+      const serverError = new Error(response.statusText)
+      // @ts-expect-error statusCode is not a standard property
+      serverError.statusCode = response.status
+
+      if (!`${args[0]}`.endsWith(ASSEMBLIES_ENDPOINT))
+        return Promise.reject(serverError)
+
+      // Failed assembly requests should return a more detailed error in JSON.
+      return response.json().then(
+        (assembly: AssemblyResponse) => {
+          if (!assembly.error) throw serverError
+
+          const error = new AssemblyError(
+            assembly.error,
+            assembly.message,
+            assembly,
+          )
+
+          if (assembly.assembly_id) {
+            error.details += ` Assembly ID: ${assembly.assembly_id}`
+          }
+          throw error
+        },
+        (err) => {
+          // eslint-disable-next-line no-param-reassign
+          err.cause = serverError
+          throw err
+        },
+      )
+    }
+
+    return response.json()
+  }
+
+  async createAssembly({
+    params,
+    fields,
+    signature,
+    expectedFiles,
+  }: OptionsWithRestructuredFields & {
+    expectedFiles: number
+  }): Promise<AssemblyResponse> {
+    const data = new FormData()
+    data.append(
+      'params',
+      typeof params === 'string' ? params : JSON.stringify(params),
+    )
+    if (signature) {
+      data.append('signature', signature)
+    }
+
+    Object.keys(fields).forEach((key) => {
+      data.append(key, String(fields[key]))
+    })
+    data.append('num_expected_upload_files', String(expectedFiles))
+
+    const url = new URL(ASSEMBLIES_ENDPOINT, `${this.opts.service}`).href
+    return this.#fetchJSON(url, {
+      method: 'POST',
+      headers: this.#headers,
+      body: data,
+    }).catch((err) => this.#reportError(err, { url, type: 'API_ERROR' }))
+  }
+
+  /**
+   * Reserve resources for a file in an Assembly. Then addFile can be used later.
+   */
+  async reserveFile(
+    assembly: AssemblyResponse,
+    file: UppyFile<M, B>,
+  ): Promise<AssemblyResponse> {
+    const size = encodeURIComponent(file.size!)
+    const url = `${assembly.assembly_ssl_url}/reserve_file?size=${size}`
+    return this.#fetchJSON(url, {
+      method: 'POST',
+      headers: this.#headers,
+    }).catch((err) =>
+      this.#reportError(err, { assembly, file, url, type: 'API_ERROR' }),
+    )
+  }
+
+  /**
+   * Import a remote file to an Assembly.
+   */
+  async addFile(
+    assembly: AssemblyResponse,
+    file: UppyFile<M, B>,
+  ): Promise<AssemblyResponse> {
+    if (!file.uploadURL) {
+      return Promise.reject(new Error('File does not have an `uploadURL`.'))
+    }
+    const size = encodeURIComponent(file.size!)
+    const uploadUrl = encodeURIComponent(file.uploadURL)
+    const filename = encodeURIComponent(file.name)
+    const fieldname = 'file'
+
+    const qs = `size=${size}&filename=${filename}&fieldname=${fieldname}&s3Url=${uploadUrl}`
+    const url = `${assembly.assembly_ssl_url}/add_file?${qs}`
+    return this.#fetchJSON(url, {
+      method: 'POST',
+      headers: this.#headers,
+    }).catch((err) =>
+      this.#reportError(err, { assembly, file, url, type: 'API_ERROR' }),
+    )
+  }
+
+  /**
+   * Update the number of expected files in an already created assembly.
+   */
+  async updateNumberOfFilesInAssembly(
+    assembly: AssemblyResponse,
+    num_expected_upload_files: number,
+  ): Promise<AssemblyResponse> {
+    const url = new URL(assembly.assembly_ssl_url)
+    url.pathname = '/update_assemblies'
+    const body = JSON.stringify({
+      assembly_updates: [
+        {
+          assembly_id: assembly.assembly_id,
+          num_expected_upload_files,
+        },
+      ],
+    })
+    return this.#fetchJSON(url, {
+      method: 'POST',
+      headers: this.#headers,
+      body,
+    }).catch((err) => this.#reportError(err, { url, type: 'API_ERROR' }))
+  }
+
+  /**
+   * Cancel a running Assembly.
+   */
+  async cancelAssembly(assembly: AssemblyResponse): Promise<AssemblyResponse> {
+    const url = assembly.assembly_ssl_url
+    return this.#fetchJSON(url, {
+      method: 'DELETE',
+      headers: this.#headers,
+    }).catch((err) => this.#reportError(err, { url, type: 'API_ERROR' }))
+  }
+
+  /**
+   * Get the current status for an assembly.
+   */
+  async getAssemblyStatus(url: string): Promise<AssemblyResponse> {
+    return this.#fetchJSON(url, { headers: this.#headers }).catch((err) =>
+      this.#reportError(err, { url, type: 'STATUS_ERROR' }),
+    )
+  }
+
+  async submitError(
+    err: { message?: string; details?: string },
+    {
+      endpoint,
+      instance,
+      assembly,
+    }: {
+      endpoint?: string | URL
+      instance?: string
+      assembly?: string
+    } = {},
+  ): Promise<AssemblyResponse> {
+    const message =
+      err.details ? `${err.message} (${err.details})` : err.message
+
+    return this.#fetchJSON('https://transloaditstatus.com/client_error', {
+      method: 'POST',
+      body: JSON.stringify({
+        endpoint,
+        instance,
+        assembly_id: assembly,
+        agent: typeof navigator !== 'undefined' ? navigator.userAgent : '',
+        client: this.opts.client,
+        error: message,
+      }),
+    })
+  }
+
+  #reportError = (
+    err: AssemblyError,
+    params: {
+      assembly?: AssemblyResponse
+      url?: URL | string
+      file?: UppyFile<M, B>
+      type: string
+    },
+  ) => {
+    if (this.opts.errorReporting === false) {
+      throw err
+    }
+
+    const opts: {
+      type: string
+      assembly?: string
+      instance?: string
+      endpoint?: URL | string
+    } = {
+      type: params.type,
+    }
+    if (params.assembly) {
+      opts.assembly = params.assembly.assembly_id
+      opts.instance = params.assembly.instance
+    }
+    if (params.url) {
+      opts.endpoint = params.url
+    }
+
+    this.submitError(err, opts).catch(() => {
+      // not much we can do then is there
+    })
+
+    throw err
+  }
+}

+ 30 - 20
packages/@uppy/transloadit/src/index.test.js

@@ -2,7 +2,7 @@ import { createServer } from 'node:http'
 import { once } from 'node:events'
 import { describe, expect, it } from 'vitest'
 import Core from '@uppy/core'
-import Transloadit from './index.js'
+import Transloadit from './index.ts'
 import 'whatwg-fetch'
 
 describe('Transloadit', () => {
@@ -30,7 +30,8 @@ describe('Transloadit', () => {
     }).toThrowError(/The `params\.auth\.key` option is required/)
     expect(() => {
       uppy.use(Transloadit, {
-        params: '{"auth":{"key":"some auth key string"},"template_id":"some template id string"}',
+        params:
+          '{"auth":{"key":"some auth key string"},"template_id":"some template id string"}',
       })
     }).not.toThrowError(/The `params\.auth\.key` option is required/)
   })
@@ -39,7 +40,7 @@ describe('Transloadit', () => {
     const error = new Error('expected failure')
     const uppy = new Core()
     uppy.use(Transloadit, {
-      getAssemblyOptions () {
+      getAssemblyOptions() {
         return Promise.reject(error)
       },
     })
@@ -50,14 +51,17 @@ describe('Transloadit', () => {
       data: new Uint8Array(100),
     })
 
-    return uppy.upload().then(() => {
-      throw new Error('Should not have succeeded')
-    }).catch((err) => {
-      const fileID = Object.keys(uppy.getState().files)[0]
+    return uppy
+      .upload()
+      .then(() => {
+        throw new Error('Should not have succeeded')
+      })
+      .catch((err) => {
+        const fileID = Object.keys(uppy.getState().files)[0]
 
-      expect(err).toBe(error)
-      expect(uppy.getFile(fileID).progress.uploadStarted).toBe(null)
-    })
+        expect(err).toBe(error)
+        expect(uppy.getFile(fileID).progress.uploadStarted).toBe(null)
+      })
   })
 
   it('Does not leave lingering progress if creating assembly fails', () => {
@@ -69,7 +73,8 @@ describe('Transloadit', () => {
       },
     })
 
-    uppy.getPlugin('Transloadit').client.createAssembly = () => Promise.reject(new Error('VIDEO_ENCODE_VALIDATION'))
+    uppy.getPlugin('Transloadit').client.createAssembly = () =>
+      Promise.reject(new Error('VIDEO_ENCODE_VALIDATION'))
 
     uppy.addFile({
       source: 'jest',
@@ -77,14 +82,19 @@ describe('Transloadit', () => {
       data: new Uint8Array(100),
     })
 
-    return uppy.upload().then(() => {
-      throw new Error('Should not have succeeded')
-    }, (err) => {
-      const fileID = Object.keys(uppy.getState().files)[0]
+    return uppy.upload().then(
+      () => {
+        throw new Error('Should not have succeeded')
+      },
+      (err) => {
+        const fileID = Object.keys(uppy.getState().files)[0]
 
-      expect(err.message).toBe('Transloadit: Could not create Assembly: VIDEO_ENCODE_VALIDATION')
-      expect(uppy.getFile(fileID).progress.uploadStarted).toBe(null)
-    })
+        expect(err.message).toBe(
+          'Transloadit: Could not create Assembly: VIDEO_ENCODE_VALIDATION',
+        )
+        expect(uppy.getFile(fileID).progress.uploadStarted).toBe(null)
+      },
+    )
   })
 
   // For some reason this test doesn't pass on CI
@@ -110,7 +120,7 @@ describe('Transloadit', () => {
 
     await uppy.upload()
     server.closeAllConnections()
-    await new Promise(resolve => server.close(resolve))
+    await new Promise((resolve) => server.close(resolve))
   })
 
   // For some reason this test doesn't pass on CI
@@ -137,6 +147,6 @@ describe('Transloadit', () => {
 
     await uppy.upload()
     server.closeAllConnections()
-    await new Promise(resolve => server.close(resolve))
+    await new Promise((resolve) => server.close(resolve))
   })
 })

Разница между файлами не показана из-за своего большого размера
+ 523 - 205
packages/@uppy/transloadit/src/index.ts


+ 0 - 0
packages/@uppy/transloadit/src/locale.js → packages/@uppy/transloadit/src/locale.ts


+ 40 - 0
packages/@uppy/transloadit/tsconfig.build.json

@@ -0,0 +1,40 @@
+{
+  "extends": "../../../tsconfig.shared",
+  "compilerOptions": {
+    "noImplicitAny": false,
+    "outDir": "./lib",
+    "paths": {
+      "@uppy/companion-client": ["../companion-client/src/index.js"],
+      "@uppy/companion-client/lib/*": ["../companion-client/src/*"],
+      "@uppy/provider-views": ["../provider-views/src/index.js"],
+      "@uppy/provider-views/lib/*": ["../provider-views/src/*"],
+      "@uppy/tus": ["../tus/src/index.js"],
+      "@uppy/tus/lib/*": ["../tus/src/*"],
+      "@uppy/utils/lib/*": ["../utils/src/*"],
+      "@uppy/core": ["../core/src/index.js"],
+      "@uppy/core/lib/*": ["../core/src/*"]
+    },
+    "resolveJsonModule": false,
+    "rootDir": "./src",
+    "skipLibCheck": true
+  },
+  "include": ["./src/**/*.*"],
+  "exclude": ["./src/**/*.test.ts"],
+  "references": [
+    {
+      "path": "../companion-client/tsconfig.build.json"
+    },
+    {
+      "path": "../provider-views/tsconfig.build.json"
+    },
+    {
+      "path": "../tus/tsconfig.build.json"
+    },
+    {
+      "path": "../utils/tsconfig.build.json"
+    },
+    {
+      "path": "../core/tsconfig.build.json"
+    }
+  ]
+}

+ 36 - 0
packages/@uppy/transloadit/tsconfig.json

@@ -0,0 +1,36 @@
+{
+  "extends": "../../../tsconfig.shared",
+  "compilerOptions": {
+    "emitDeclarationOnly": false,
+    "noEmit": true,
+    "paths": {
+      "@uppy/companion-client": ["../companion-client/src/index.js"],
+      "@uppy/companion-client/lib/*": ["../companion-client/src/*"],
+      "@uppy/provider-views": ["../provider-views/src/index.js"],
+      "@uppy/provider-views/lib/*": ["../provider-views/src/*"],
+      "@uppy/tus": ["../tus/src/index.js"],
+      "@uppy/tus/lib/*": ["../tus/src/*"],
+      "@uppy/utils/lib/*": ["../utils/src/*"],
+      "@uppy/core": ["../core/src/index.js"],
+      "@uppy/core/lib/*": ["../core/src/*"],
+    },
+  },
+  "include": ["./package.json", "./src/**/*.*"],
+  "references": [
+    {
+      "path": "../companion-client/tsconfig.build.json",
+    },
+    {
+      "path": "../provider-views/tsconfig.build.json",
+    },
+    {
+      "path": "../tus/tsconfig.build.json",
+    },
+    {
+      "path": "../utils/tsconfig.build.json",
+    },
+    {
+      "path": "../core/tsconfig.build.json",
+    },
+  ],
+}

+ 2 - 0
packages/@uppy/tus/src/index.ts

@@ -36,6 +36,8 @@ type RestTusUploadOptions = Omit<
   'onShouldRetry' | 'onBeforeRequest' | 'headers'
 >
 
+export type TusDetailedError = tus.DetailedError
+
 export interface TusOpts<M extends Meta, B extends Body>
   extends PluginOpts,
     RestTusUploadOptions {

Некоторые файлы не были показаны из-за большого количества измененных файлов