From 79e6460a6cd73a47bf7943fa5c45ad617fe1154f Mon Sep 17 00:00:00 2001 From: Prakash Date: Mon, 17 Nov 2025 18:18:54 +0530 Subject: [PATCH] Make Generics Optional in uppy.getPlugin (#6057) fixes #6024. ### Problem - `getPlugin()` defaults to `UnknownPlugin`, so methods like `openModal` are not visible , since core is not aware of that plugin type ### Proposed change - Introduce a types-only registry in core: - `export interface PluginTypeRegistry {}` - Overload `getPlugin` to return a precise type when the id is a known key of the registry. - add `Dashboard` to PluginTypeRegistry through module augmentation: - `'Dashboard': Dashboard`. - When a project imports from `@uppy/dashboard`, its module augmentation extends PluginTypeRegistry, adding the correct type into it - I've added Tests , kept them in a separate file so it's easier to review , once this approach gets approved I'll add them to `Uppy.test.ts` Once this PR gets a positive review I'll add this for other plugins , currently only added for `@uppy/dashboard` **Build with Local tarball can be checked here** https://stackblitz.com/~/github.com/qxprakash/uppy-type-test?file=type_test.ts --- .changeset/nasty-friends-win.md | 36 ++++++++++ packages/@uppy/audio/src/Audio.tsx | 6 ++ packages/@uppy/aws-s3/src/index.test.ts | 30 ++++---- packages/@uppy/aws-s3/src/index.ts | 3 + packages/@uppy/box/src/Box.tsx | 6 ++ packages/@uppy/compressor/src/index.ts | 3 + packages/@uppy/core/src/Uppy.ts | 17 ++++- packages/@uppy/core/src/index.ts | 1 + packages/@uppy/core/src/types.test.ts | 70 +++++++++++++++++++ packages/@uppy/dashboard/src/Dashboard.tsx | 4 ++ .../src/GlobalSearch.browser.test.ts | 2 +- packages/@uppy/drag-drop/src/DragDrop.tsx | 6 ++ packages/@uppy/drop-target/src/index.ts | 6 ++ packages/@uppy/dropbox/src/Dropbox.tsx | 6 ++ packages/@uppy/facebook/src/Facebook.tsx | 6 ++ packages/@uppy/form/src/index.ts | 6 ++ .../src/goldenRetriever.browser.test.ts | 6 +- packages/@uppy/golden-retriever/src/index.ts | 3 + .../src/GoogleDrivePicker.tsx | 6 ++ .../@uppy/google-drive/src/GoogleDrive.tsx | 6 ++ .../src/GooglePhotosPicker.tsx | 6 ++ .../@uppy/image-editor/src/ImageEditor.tsx | 6 ++ packages/@uppy/instagram/src/Instagram.tsx | 6 ++ packages/@uppy/onedrive/src/OneDrive.tsx | 6 ++ packages/@uppy/react/src/DashboardModal.ts | 2 +- packages/@uppy/remote-sources/src/index.ts | 6 ++ .../screen-capture/src/ScreenCapture.tsx | 6 ++ packages/@uppy/status-bar/src/StatusBar.tsx | 6 ++ .../src/lib/components/Dashboard.svelte | 2 +- .../src/lib/components/DashboardModal.svelte | 2 +- .../src/lib/components/StatusBar.svelte | 2 +- .../@uppy/thumbnail-generator/src/index.ts | 9 +++ packages/@uppy/transloadit/src/index.ts | 4 ++ packages/@uppy/tus/src/index.ts | 6 ++ packages/@uppy/unsplash/src/Unsplash.tsx | 6 ++ packages/@uppy/url/src/Url.tsx | 6 ++ packages/@uppy/vue/src/dashboard-modal.ts | 2 +- packages/@uppy/vue/src/dashboard.ts | 2 +- packages/@uppy/vue/src/status-bar.ts | 2 +- packages/@uppy/webcam/src/Webcam.test.ts | 25 +++---- packages/@uppy/webcam/src/Webcam.tsx | 6 ++ packages/@uppy/webdav/src/Webdav.tsx | 6 ++ packages/@uppy/xhr-upload/src/index.ts | 6 ++ packages/@uppy/zoom/src/Zoom.tsx | 6 ++ 44 files changed, 320 insertions(+), 45 deletions(-) create mode 100644 .changeset/nasty-friends-win.md diff --git a/.changeset/nasty-friends-win.md b/.changeset/nasty-friends-win.md new file mode 100644 index 000000000..fb9a948fa --- /dev/null +++ b/.changeset/nasty-friends-win.md @@ -0,0 +1,36 @@ +--- +"@uppy/google-photos-picker": minor +"@uppy/google-drive-picker": minor +"@uppy/thumbnail-generator": minor +"@uppy/golden-retriever": minor +"@uppy/provider-views": minor +"@uppy/remote-sources": minor +"@uppy/screen-capture": minor +"@uppy/google-drive": minor +"@uppy/image-editor": minor +"@uppy/drop-target": minor +"@uppy/transloadit": minor +"@uppy/compressor": minor +"@uppy/status-bar": minor +"@uppy/xhr-upload": minor +"@uppy/dashboard": minor +"@uppy/drag-drop": minor +"@uppy/instagram": minor +"@uppy/facebook": minor +"@uppy/onedrive": minor +"@uppy/unsplash": minor +"@uppy/dropbox": minor +"@uppy/aws-s3": minor +"@uppy/webcam": minor +"@uppy/webdav": minor +"@uppy/audio": minor +"@uppy/core": minor +"@uppy/form": minor +"@uppy/zoom": minor +"@uppy/box": minor +"@uppy/tus": minor +"@uppy/url": minor +--- + +- Add PluginTypeRegistry and typed getPlugin overload in @uppy/core +- Register plugin ids across packages so uppy.getPlugin('Dashboard' | 'Webcam') returns the concrete plugin type and removes the need to pass generics in getPlugin() \ No newline at end of file diff --git a/packages/@uppy/audio/src/Audio.tsx b/packages/@uppy/audio/src/Audio.tsx index 1ef4374f2..f998c9ef0 100644 --- a/packages/@uppy/audio/src/Audio.tsx +++ b/packages/@uppy/audio/src/Audio.tsx @@ -15,6 +15,12 @@ import PermissionsScreen from './PermissionsScreen.js' import RecordingScreen from './RecordingScreen.js' import supportsMediaRecorder from './supportsMediaRecorder.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Audio: Audio + } +} + export interface AudioOptions extends UIPluginOptions { showAudioSourceDropdown?: boolean locale?: LocaleStrings diff --git a/packages/@uppy/aws-s3/src/index.test.ts b/packages/@uppy/aws-s3/src/index.test.ts index aa3dc6aac..f3061f79a 100644 --- a/packages/@uppy/aws-s3/src/index.test.ts +++ b/packages/@uppy/aws-s3/src/index.test.ts @@ -9,7 +9,7 @@ import { } from 'vitest' import 'whatwg-fetch' -import Core, { type UppyFile } from '@uppy/core' +import Core, { type Meta, type UppyFile } from '@uppy/core' import nock from 'nock' import AwsS3Multipart, { type AwsBody, @@ -36,8 +36,8 @@ describe('AwsS3Multipart', () => { let opts: Partial> beforeEach(() => { - const core = new Core().use(AwsS3Multipart) - const awsS3Multipart = core.getPlugin('AwsS3Multipart') as any + const core = new Core().use(AwsS3Multipart) + const awsS3Multipart = core.getPlugin('AwsS3Multipart')! opts = awsS3Multipart.opts }) @@ -125,8 +125,8 @@ describe('AwsS3Multipart', () => { const awsS3Multipart = core.getPlugin('AwsS3Multipart')! const err = 'Expected a `endpoint` option' - const file = {} - const opts = {} + const file = {} as unknown as UppyFile> + const opts = {} as any expect(() => awsS3Multipart.opts.createMultipartUpload(file)).toThrow(err) expect(() => awsS3Multipart.opts.listParts(file, opts)).toThrow(err) @@ -202,11 +202,11 @@ describe('AwsS3Multipart', () => { }) describe('without companionUrl (custom main functions)', () => { - let core: Core - let awsS3Multipart: AwsS3Multipart + let core: Core + let awsS3Multipart: AwsS3Multipart beforeEach(() => { - core = new Core() + core = new Core() core.use(AwsS3Multipart, { shouldUseMultipart: true, limit: 0, @@ -226,7 +226,7 @@ describe('AwsS3Multipart', () => { }), listParts: undefined as any, }) - awsS3Multipart = core.getPlugin('AwsS3Multipart') as any + awsS3Multipart = core.getPlugin('AwsS3Multipart')! }) it('Keeps chunks marked as busy through retries until they complete', async () => { @@ -366,7 +366,6 @@ describe('AwsS3Multipart', () => { ), listParts: undefined as any, }) - const awsS3Multipart = core.getPlugin('AwsS3Multipart')! const fileSize = 5 * MB + 1 * MB core.addFile({ @@ -380,7 +379,7 @@ describe('AwsS3Multipart', () => { await core.upload() - expect(awsS3Multipart.opts.uploadPartBytes.mock.calls.length).toEqual(3) + expect(uploadPartBytes.mock.calls.length).toEqual(3) }) it('calls `upload-error` when uploadPartBytes fails after all retries', async () => { @@ -406,7 +405,6 @@ describe('AwsS3Multipart', () => { listParts: undefined as any, }) const fileSize = 5 * MB + 1 * MB - const awsS3Multipart = core.getPlugin('AwsS3Multipart')! const uploadErrorMock = vi.fn() const uploadSuccessMock = vi.fn() core.on('upload-error', uploadErrorMock) @@ -438,7 +436,7 @@ describe('AwsS3Multipart', () => { // Catch Promise.all reject } - expect(awsS3Multipart.opts.uploadPartBytes.mock.calls.length).toEqual(5) + expect(uploadPartBytes.mock.calls.length).toEqual(5) expect(uploadErrorMock.mock.calls.length).toEqual(1) expect(uploadSuccessMock.mock.calls.length).toEqual(1) // This fails for me becuase upload returned early. }) @@ -526,8 +524,8 @@ describe('AwsS3Multipart', () => { }) describe('dynamic companionHeader using setOption', () => { - let core: Core - let awsS3Multipart: AwsS3Multipart + let core: Core + let awsS3Multipart: AwsS3Multipart const newToken = 'new token' it('companionHeader is updated before uploading file', async () => { @@ -536,7 +534,7 @@ describe('AwsS3Multipart', () => { /* Set up preprocessor */ core.addPreProcessor(() => { awsS3Multipart = - core.getPlugin>('AwsS3Multipart')! + core.getPlugin>('AwsS3Multipart')! awsS3Multipart.setOptions({ endpoint: 'http://localhost', headers: { diff --git a/packages/@uppy/aws-s3/src/index.ts b/packages/@uppy/aws-s3/src/index.ts index 48b977bf9..9f0dde6ae 100644 --- a/packages/@uppy/aws-s3/src/index.ts +++ b/packages/@uppy/aws-s3/src/index.ts @@ -45,6 +45,9 @@ declare module '@uppy/core' { export interface UppyEventMap { 's3-multipart:part-uploaded': PartUploadedCallback } + export interface PluginTypeRegistry { + AwsS3Multipart: AwsS3Multipart + } } function assertServerError(res: T): T { diff --git a/packages/@uppy/box/src/Box.tsx b/packages/@uppy/box/src/Box.tsx index 9776530b2..590ebc3e1 100644 --- a/packages/@uppy/box/src/Box.tsx +++ b/packages/@uppy/box/src/Box.tsx @@ -109,3 +109,9 @@ export default class Box return this.view.render(state) } } + +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Box: Box + } +} diff --git a/packages/@uppy/compressor/src/index.ts b/packages/@uppy/compressor/src/index.ts index 0d02edcf3..c1951e7b1 100644 --- a/packages/@uppy/compressor/src/index.ts +++ b/packages/@uppy/compressor/src/index.ts @@ -11,6 +11,9 @@ declare module '@uppy/core' { export interface UppyEventMap { 'compressor:complete': (file: UppyFile[]) => void } + export interface PluginTypeRegistry { + Compressor: Compressor + } } export interface CompressorOpts extends PluginOpts, CompressorJS.Options { diff --git a/packages/@uppy/core/src/Uppy.ts b/packages/@uppy/core/src/Uppy.ts index e8b245703..b09add1db 100644 --- a/packages/@uppy/core/src/Uppy.ts +++ b/packages/@uppy/core/src/Uppy.ts @@ -142,6 +142,10 @@ export type UnknownProviderPluginState = { searchResults?: string[] | undefined } +// biome-ignore lint/suspicious/noEmptyInterface: PluginTypeRegistry is extended via module augmentation +// biome-ignore lint/correctness/noUnusedVariables: Type parameters are used in module augmentation +export interface PluginTypeRegistry {} + export interface AsyncStore { getItem: (key: string) => Promise setItem: (key: string, value: string) => Promise @@ -1941,12 +1945,21 @@ export class Uppy< /** * Find one Plugin by name. */ + + getPlugin>( + id: K, + ): PluginTypeRegistry[K] | undefined + getPlugin = UnknownPlugin>( id: string, - ): T | undefined { + ): T | undefined + + getPlugin(id: string): UnknownPlugin | undefined { for (const plugins of Object.values(this.#plugins)) { const foundPlugin = plugins.find((plugin) => plugin.id === id) - if (foundPlugin != null) return foundPlugin as T + if (foundPlugin != null) { + return foundPlugin as UnknownPlugin + } } return undefined } diff --git a/packages/@uppy/core/src/index.ts b/packages/@uppy/core/src/index.ts index 70d844346..961bdd1a5 100644 --- a/packages/@uppy/core/src/index.ts +++ b/packages/@uppy/core/src/index.ts @@ -22,6 +22,7 @@ export type { PartialTreeFolderNode, PartialTreeFolderRoot, PartialTreeId, + PluginTypeRegistry, State, UnknownPlugin, UnknownProviderPlugin, diff --git a/packages/@uppy/core/src/types.test.ts b/packages/@uppy/core/src/types.test.ts index 2c621cbf9..7e8450b81 100644 --- a/packages/@uppy/core/src/types.test.ts +++ b/packages/@uppy/core/src/types.test.ts @@ -1,5 +1,6 @@ import type { Body, InternalMetadata, LocaleStrings, Meta } from '@uppy/utils' import { expectTypeOf, test } from 'vitest' +import BasePlugin from './BasePlugin.js' import UIPlugin, { type UIPluginOptions } from './UIPlugin.js' import Uppy, { type UnknownPlugin } from './Uppy.js' @@ -68,3 +69,72 @@ test('Meta and Body generic move through the Uppy class', async () => { await core.upload() }) + +class TestRegistryPlugin extends BasePlugin< + Record, + M, + B +> { + constructor(uppy: Uppy) { + super(uppy, {}) + this.id = 'TestRegistryPlugin' + this.type = 'acquirer' + } +} + +// Augment the Type Registry with our test plugin +declare module './Uppy.js' { + interface PluginTypeRegistry { + TestRegistryPlugin: TestRegistryPlugin + } +} + +test('Type Registry: getPlugin with registered plugin name returns correct type', () => { + const uppy = new Uppy() + uppy.use(TestRegistryPlugin) + + // When using a registered plugin name, TypeScript should infer the correct type from PluginTypeRegistry + const plugin = uppy.getPlugin('TestRegistryPlugin') + + expectTypeOf(plugin).toEqualTypeOf< + TestRegistryPlugin> | undefined + >() +}) + +test('Type Registry: getPlugin with unregistered name returns UnknownPlugin', () => { + const uppy = new Uppy() + + // When using a non-registered string, should return UnknownPlugin + const plugin = uppy.getPlugin('SomeRandomPlugin') + + expectTypeOf(plugin).toEqualTypeOf< + UnknownPlugin> | undefined + >() +}) + +test('Type Registry: getPlugin with dynamic string returns UnknownPlugin', () => { + const uppy = new Uppy() + const pluginName: string = 'DynamicName' + + // Dynamic string should use the fallback overload unlike literal string + const plugin = uppy.getPlugin(pluginName) + + expectTypeOf(plugin).toEqualTypeOf< + UnknownPlugin> | undefined + >() +}) + +test('Type Registry: works with custom Meta and Body types', () => { + type CustomMeta = { userId: string; timestamp: number } + type CustomBody = { encrypted: boolean } + + // With custom Meta and Body types + const uppy = new Uppy() + uppy.use(TestRegistryPlugin) + + const plugin = uppy.getPlugin('TestRegistryPlugin') + + expectTypeOf(plugin).toEqualTypeOf< + TestRegistryPlugin | undefined + >() +}) diff --git a/packages/@uppy/dashboard/src/Dashboard.tsx b/packages/@uppy/dashboard/src/Dashboard.tsx index 6aeb23780..f3aa11973 100644 --- a/packages/@uppy/dashboard/src/Dashboard.tsx +++ b/packages/@uppy/dashboard/src/Dashboard.tsx @@ -40,6 +40,10 @@ declare module '@uppy/core' { 'dashboard:file-edit-complete': DashboardFileEditCompleteCallback 'dashboard:close-panel': (id: string | undefined) => void } + + export interface PluginTypeRegistry { + Dashboard: Dashboard + } } interface PromiseWithResolvers { diff --git a/packages/@uppy/dashboard/src/GlobalSearch.browser.test.ts b/packages/@uppy/dashboard/src/GlobalSearch.browser.test.ts index 1b2050e56..16eaa00df 100644 --- a/packages/@uppy/dashboard/src/GlobalSearch.browser.test.ts +++ b/packages/@uppy/dashboard/src/GlobalSearch.browser.test.ts @@ -62,7 +62,7 @@ afterEach(async () => { // this is done to prevent the edgecase when all plugins are removed before dashboard is unmounted from UI // causing PickerPanelContent to crash - const dashboard = uppy.getPlugin('Dashboard') as Dashboard + const dashboard = uppy.getPlugin('Dashboard') dashboard?.hideAllPanels() const panelSelector = '[data-uppy-panelType="PickerPanel"]' if (document.querySelector(panelSelector)) { diff --git a/packages/@uppy/drag-drop/src/DragDrop.tsx b/packages/@uppy/drag-drop/src/DragDrop.tsx index b4bd2a253..8987f64fd 100644 --- a/packages/@uppy/drag-drop/src/DragDrop.tsx +++ b/packages/@uppy/drag-drop/src/DragDrop.tsx @@ -12,6 +12,12 @@ import type { ComponentChild, h } from 'preact' import packageJson from '../package.json' with { type: 'json' } import locale from './locale.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + DragDrop: DragDrop + } +} + export interface DragDropOptions extends UIPluginOptions { inputName?: string allowMultipleFiles?: boolean diff --git a/packages/@uppy/drop-target/src/index.ts b/packages/@uppy/drop-target/src/index.ts index 37667519b..abbb09e7b 100644 --- a/packages/@uppy/drop-target/src/index.ts +++ b/packages/@uppy/drop-target/src/index.ts @@ -3,6 +3,12 @@ import { BasePlugin } from '@uppy/core' import { getDroppedFiles, toArray } from '@uppy/utils' import packageJson from '../package.json' with { type: 'json' } +declare module '@uppy/core' { + export interface PluginTypeRegistry { + DropTarget: DropTarget + } +} + export interface DropTargetOptions extends PluginOpts { target?: HTMLElement | string | null onDrop?: (event: DragEvent) => void diff --git a/packages/@uppy/dropbox/src/Dropbox.tsx b/packages/@uppy/dropbox/src/Dropbox.tsx index baa479f88..e536d09e3 100644 --- a/packages/@uppy/dropbox/src/Dropbox.tsx +++ b/packages/@uppy/dropbox/src/Dropbox.tsx @@ -21,6 +21,12 @@ import { type ComponentChild, h } from 'preact' import packageJson from '../package.json' with { type: 'json' } import locale from './locale.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Dropbox: Dropbox + } +} + export type DropboxOptions = CompanionPluginOptions & { locale?: LocaleStrings } diff --git a/packages/@uppy/facebook/src/Facebook.tsx b/packages/@uppy/facebook/src/Facebook.tsx index f38b9229f..7032db1e4 100644 --- a/packages/@uppy/facebook/src/Facebook.tsx +++ b/packages/@uppy/facebook/src/Facebook.tsx @@ -21,6 +21,12 @@ import { type ComponentChild, h } from 'preact' import packageJson from '../package.json' with { type: 'json' } import locale from './locale.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Facebook: Facebook + } +} + export type FacebookOptions = CompanionPluginOptions & { locale?: LocaleStrings } diff --git a/packages/@uppy/form/src/index.ts b/packages/@uppy/form/src/index.ts index 661708e6f..798fff854 100644 --- a/packages/@uppy/form/src/index.ts +++ b/packages/@uppy/form/src/index.ts @@ -14,6 +14,12 @@ import getFormData from 'get-form-data' import packageJson from '../package.json' with { type: 'json' } +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Form: Form + } +} + type Result = Parameters< UppyEventMap['complete'] >[0] diff --git a/packages/@uppy/golden-retriever/src/goldenRetriever.browser.test.ts b/packages/@uppy/golden-retriever/src/goldenRetriever.browser.test.ts index c62197e35..1ea91c3c5 100644 --- a/packages/@uppy/golden-retriever/src/goldenRetriever.browser.test.ts +++ b/packages/@uppy/golden-retriever/src/goldenRetriever.browser.test.ts @@ -207,9 +207,9 @@ describe('Golden retriever', () => { // Simulate ghosting of the files by deleting it from store(s) // @ts-expect-error - ;(uppy.getPlugin('GoldenRetriever') as GoldenRetriever)[ - Symbol.for('uppy test: deleteBlobs') - ](fileIds) + uppy + .getPlugin('GoldenRetriever') + [Symbol.for('uppy test: deleteBlobs')](fileIds) // reload page and recreate Uppy instance uppy = createUppy({ withPageReload: true }).use(GoldenRetriever) diff --git a/packages/@uppy/golden-retriever/src/index.ts b/packages/@uppy/golden-retriever/src/index.ts index 05bb6d51e..a28ae3cd2 100644 --- a/packages/@uppy/golden-retriever/src/index.ts +++ b/packages/@uppy/golden-retriever/src/index.ts @@ -19,6 +19,9 @@ declare module '@uppy/core' { export interface UppyEventMap { 'restore:plugin-data-changed': (data: Record) => void } + export interface PluginTypeRegistry { + GoldenRetriever: GoldenRetriever + } } export interface GoldenRetrieverOptions extends PluginOpts { diff --git a/packages/@uppy/google-drive-picker/src/GoogleDrivePicker.tsx b/packages/@uppy/google-drive-picker/src/GoogleDrivePicker.tsx index 70791634e..6d378afdf 100644 --- a/packages/@uppy/google-drive-picker/src/GoogleDrivePicker.tsx +++ b/packages/@uppy/google-drive-picker/src/GoogleDrivePicker.tsx @@ -15,6 +15,12 @@ import type { LocaleStrings } from '@uppy/utils' import packageJson from '../package.json' with { type: 'json' } import locale from './locale.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + GoogleDrivePicker: GoogleDrivePicker + } +} + export type GoogleDrivePickerOptions = CompanionPluginOptions & { clientId: string apiKey: string diff --git a/packages/@uppy/google-drive/src/GoogleDrive.tsx b/packages/@uppy/google-drive/src/GoogleDrive.tsx index b002a3d99..1d43160ba 100644 --- a/packages/@uppy/google-drive/src/GoogleDrive.tsx +++ b/packages/@uppy/google-drive/src/GoogleDrive.tsx @@ -22,6 +22,12 @@ import packageJson from '../package.json' with { type: 'json' } import DriveProviderViews from './DriveProviderViews.js' import locale from './locale.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + GoogleDrive: GoogleDrive + } +} + export type GoogleDriveOptions = CompanionPluginOptions & { locale?: LocaleStrings } diff --git a/packages/@uppy/google-photos-picker/src/GooglePhotosPicker.tsx b/packages/@uppy/google-photos-picker/src/GooglePhotosPicker.tsx index 10dcba483..0242358ae 100644 --- a/packages/@uppy/google-photos-picker/src/GooglePhotosPicker.tsx +++ b/packages/@uppy/google-photos-picker/src/GooglePhotosPicker.tsx @@ -15,6 +15,12 @@ import type { LocaleStrings } from '@uppy/utils' import packageJson from '../package.json' with { type: 'json' } import locale from './locale.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + GooglePhotosPicker: GooglePhotosPicker + } +} + export type GooglePhotosPickerOptions = CompanionPluginOptions & { clientId: string locale?: LocaleStrings diff --git a/packages/@uppy/image-editor/src/ImageEditor.tsx b/packages/@uppy/image-editor/src/ImageEditor.tsx index fceb0bded..976f5435d 100644 --- a/packages/@uppy/image-editor/src/ImageEditor.tsx +++ b/packages/@uppy/image-editor/src/ImageEditor.tsx @@ -13,6 +13,12 @@ import packageJson from '../package.json' with { type: 'json' } import Editor from './Editor.js' import locale from './locale.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + ImageEditor: ImageEditor + } +} + declare global { namespace preact { interface Component { diff --git a/packages/@uppy/instagram/src/Instagram.tsx b/packages/@uppy/instagram/src/Instagram.tsx index 48160d630..9f56b0379 100644 --- a/packages/@uppy/instagram/src/Instagram.tsx +++ b/packages/@uppy/instagram/src/Instagram.tsx @@ -21,6 +21,12 @@ import { type ComponentChild, h } from 'preact' import packageJson from '../package.json' with { type: 'json' } import locale from './locale.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Instagram: Instagram + } +} + export type InstagramOptions = CompanionPluginOptions & { locale?: LocaleStrings } diff --git a/packages/@uppy/onedrive/src/OneDrive.tsx b/packages/@uppy/onedrive/src/OneDrive.tsx index dc1a411aa..9a47c812d 100644 --- a/packages/@uppy/onedrive/src/OneDrive.tsx +++ b/packages/@uppy/onedrive/src/OneDrive.tsx @@ -122,3 +122,9 @@ export default class OneDrive return this.view.render(state) } } + +declare module '@uppy/core' { + export interface PluginTypeRegistry { + OneDrive: OneDrive + } +} diff --git a/packages/@uppy/react/src/DashboardModal.ts b/packages/@uppy/react/src/DashboardModal.ts index 4a8ee3499..12dec977d 100644 --- a/packages/@uppy/react/src/DashboardModal.ts +++ b/packages/@uppy/react/src/DashboardModal.ts @@ -82,7 +82,7 @@ class DashboardModal extends Component< uppy.use(DashboardPlugin, options) - this.plugin = uppy.getPlugin(options.id) as DashboardPlugin + this.plugin = uppy.getPlugin(options.id)! if (open) { this.plugin.openModal() } diff --git a/packages/@uppy/remote-sources/src/index.ts b/packages/@uppy/remote-sources/src/index.ts index 56f3e4a28..91d9fbefb 100644 --- a/packages/@uppy/remote-sources/src/index.ts +++ b/packages/@uppy/remote-sources/src/index.ts @@ -122,3 +122,9 @@ export default class RemoteSources< this.#installedPlugins.clear() } } + +declare module '@uppy/core' { + export interface PluginTypeRegistry { + RemoteSources: RemoteSources + } +} diff --git a/packages/@uppy/screen-capture/src/ScreenCapture.tsx b/packages/@uppy/screen-capture/src/ScreenCapture.tsx index 93cb29071..e99edfd2d 100644 --- a/packages/@uppy/screen-capture/src/ScreenCapture.tsx +++ b/packages/@uppy/screen-capture/src/ScreenCapture.tsx @@ -14,6 +14,12 @@ import locale from './locale.js' import RecorderScreen from './RecorderScreen.js' import ScreenRecIcon from './ScreenRecIcon.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + ScreenCapture: ScreenCapture + } +} + // Check if screen capturing is supported. // mediaDevices is supprted on mobile Safari, getDisplayMedia is not function isScreenRecordingSupported() { diff --git a/packages/@uppy/status-bar/src/StatusBar.tsx b/packages/@uppy/status-bar/src/StatusBar.tsx index f4eb13603..07d882744 100644 --- a/packages/@uppy/status-bar/src/StatusBar.tsx +++ b/packages/@uppy/status-bar/src/StatusBar.tsx @@ -15,6 +15,12 @@ import type { StatusBarOptions } from './StatusBarOptions.js' import statusBarStates from './StatusBarStates.js' import StatusBarUI, { type StatusBarUIProps } from './StatusBarUI.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + StatusBar: StatusBar + } +} + const speedFilterHalfLife = 2000 const ETAFilterHalfLife = 2000 diff --git a/packages/@uppy/svelte/src/lib/components/Dashboard.svelte b/packages/@uppy/svelte/src/lib/components/Dashboard.svelte index 7b501b29b..d56b88718 100644 --- a/packages/@uppy/svelte/src/lib/components/Dashboard.svelte +++ b/packages/@uppy/svelte/src/lib/components/Dashboard.svelte @@ -23,7 +23,7 @@ const installPlugin = () => { } satisfies DashboardOptions; uppy.use(DashboardPlugin, options); - plugin = uppy.getPlugin(options.id) as DashboardPlugin; + plugin = uppy.getPlugin(options.id)!; }; const uninstallPlugin = (uppyInstance: Uppy = uppy) => { if (plugin != null) uppyInstance.removePlugin(plugin); diff --git a/packages/@uppy/svelte/src/lib/components/DashboardModal.svelte b/packages/@uppy/svelte/src/lib/components/DashboardModal.svelte index e3ae3d4ad..5383541b0 100644 --- a/packages/@uppy/svelte/src/lib/components/DashboardModal.svelte +++ b/packages/@uppy/svelte/src/lib/components/DashboardModal.svelte @@ -25,7 +25,7 @@ const installPlugin = () => { } satisfies DashboardOptions; uppy.use(DashboardPlugin, options); - plugin = uppy.getPlugin(options.id) as DashboardPlugin; + plugin = uppy.getPlugin(options.id)!; if (open) plugin.openModal(); }; const uninstallPlugin = (uppyInstance: Uppy = uppy) => { diff --git a/packages/@uppy/svelte/src/lib/components/StatusBar.svelte b/packages/@uppy/svelte/src/lib/components/StatusBar.svelte index 714e297e4..328a22d3f 100644 --- a/packages/@uppy/svelte/src/lib/components/StatusBar.svelte +++ b/packages/@uppy/svelte/src/lib/components/StatusBar.svelte @@ -20,7 +20,7 @@ const installPlugin = () => { } satisfies StatusBarOptions; uppy.use(StatusBarPlugin, options); - plugin = uppy.getPlugin(options.id) as StatusBarPlugin; + plugin = uppy.getPlugin(options.id)!; }; const uninstallPlugin = (uppyInstance: Uppy = uppy) => { if (plugin != null) uppyInstance.removePlugin(plugin); diff --git a/packages/@uppy/thumbnail-generator/src/index.ts b/packages/@uppy/thumbnail-generator/src/index.ts index 332e6af27..3492c7d78 100644 --- a/packages/@uppy/thumbnail-generator/src/index.ts +++ b/packages/@uppy/thumbnail-generator/src/index.ts @@ -15,6 +15,9 @@ declare module '@uppy/core' { 'thumbnail:request': (file: UppyFile) => void 'thumbnail:cancel': (file: UppyFile) => void } + export interface PluginTypeRegistry { + ThumbnailGenerator: ThumbnailGenerator + } } interface Rotation { @@ -500,3 +503,9 @@ export default class ThumbnailGenerator< } } } + +declare module '@uppy/core' { + export interface PluginTypeRegistry { + ThumbnailGenerator: ThumbnailGenerator + } +} diff --git a/packages/@uppy/transloadit/src/index.ts b/packages/@uppy/transloadit/src/index.ts index 216f2fb95..21544ab52 100644 --- a/packages/@uppy/transloadit/src/index.ts +++ b/packages/@uppy/transloadit/src/index.ts @@ -132,6 +132,10 @@ declare module '@uppy/core' { progress_combined?: number }) => void } + + export interface PluginTypeRegistry { + Transloadit: Transloadit + } } declare module '@uppy/utils' { diff --git a/packages/@uppy/tus/src/index.ts b/packages/@uppy/tus/src/index.ts index f8fd7c52f..1b9695ed4 100644 --- a/packages/@uppy/tus/src/index.ts +++ b/packages/@uppy/tus/src/index.ts @@ -106,6 +106,12 @@ declare module '@uppy/utils' { } } +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Tus: Tus + } +} + /** * Tus resumable file uploader */ diff --git a/packages/@uppy/unsplash/src/Unsplash.tsx b/packages/@uppy/unsplash/src/Unsplash.tsx index a14fd49b0..8dccee16b 100644 --- a/packages/@uppy/unsplash/src/Unsplash.tsx +++ b/packages/@uppy/unsplash/src/Unsplash.tsx @@ -112,3 +112,9 @@ export default class Unsplash this.unmount() } } + +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Unsplash: Unsplash + } +} diff --git a/packages/@uppy/url/src/Url.tsx b/packages/@uppy/url/src/Url.tsx index 437cea61a..76d1873d7 100644 --- a/packages/@uppy/url/src/Url.tsx +++ b/packages/@uppy/url/src/Url.tsx @@ -17,6 +17,12 @@ import locale from './locale.js' import UrlUI from './UrlUI.js' import forEachDroppedOrPastedUrl from './utils/forEachDroppedOrPastedUrl.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Url: Url + } +} + function UrlIcon() { return ( + pluginRef.value = uppy.getPlugin(options.id)! } useUppy(onMount, pluginRef, props.uppy, propsRef) diff --git a/packages/@uppy/vue/src/dashboard.ts b/packages/@uppy/vue/src/dashboard.ts index 03301ab7e..f06ca3f54 100644 --- a/packages/@uppy/vue/src/dashboard.ts +++ b/packages/@uppy/vue/src/dashboard.ts @@ -32,7 +32,7 @@ export default defineComponent({ target: containerRef.value, } uppy.use(DashboardPlugin, options) - pluginRef.value = uppy.getPlugin(options.id) as DashboardPlugin + pluginRef.value = uppy.getPlugin(options.id)! } useUppy(onMount, pluginRef, props.uppy, propsRef) diff --git a/packages/@uppy/vue/src/status-bar.ts b/packages/@uppy/vue/src/status-bar.ts index 3e7138323..106d8302d 100644 --- a/packages/@uppy/vue/src/status-bar.ts +++ b/packages/@uppy/vue/src/status-bar.ts @@ -26,7 +26,7 @@ export default defineComponent({ target: containerRef.value, } uppy.use(StatusBarPlugin, options) - pluginRef.value = uppy.getPlugin(options.id) as StatusBarPlugin + pluginRef.value = uppy.getPlugin(options.id)! } useUppy(onMount, pluginRef, props.uppy, propsRef) diff --git a/packages/@uppy/webcam/src/Webcam.test.ts b/packages/@uppy/webcam/src/Webcam.test.ts index d1b5464ac..b181f91ec 100644 --- a/packages/@uppy/webcam/src/Webcam.test.ts +++ b/packages/@uppy/webcam/src/Webcam.test.ts @@ -1,4 +1,4 @@ -import Uppy from '@uppy/core' +import { Uppy } from '@uppy/core' import { describe, expect, it } from 'vitest' import Webcam from './index.js' @@ -10,10 +10,9 @@ describe('Webcam', () => { isTypeSupported: () => true, } - const uppy = new Uppy().use(Webcam) + const uppy = new Uppy().use(Webcam) expect( - (uppy.getPlugin('Webcam') as Webcam).getMediaRecorderOptions() - .mimeType, + uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType, ).not.toBeDefined() }) @@ -27,8 +26,7 @@ describe('Webcam', () => { preferredVideoMimeType: 'video/webm', }) expect( - (uppy.getPlugin('Webcam') as Webcam).getMediaRecorderOptions() - .mimeType, + uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType, ).toEqual('video/webm') }) @@ -42,8 +40,7 @@ describe('Webcam', () => { preferredVideoMimeType: 'video/mp4', }) expect( - (uppy.getPlugin('Webcam') as Webcam).getMediaRecorderOptions() - .mimeType, + uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType, ).not.toBeDefined() }) @@ -57,8 +54,7 @@ describe('Webcam', () => { restrictions: { allowedFileTypes: ['video/mp4', 'video/webm'] }, }).use(Webcam) expect( - (uppy.getPlugin('Webcam') as Webcam).getMediaRecorderOptions() - .mimeType, + uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType, ).toEqual('video/mp4') }) @@ -72,8 +68,7 @@ describe('Webcam', () => { restrictions: { allowedFileTypes: ['video/mp4', 'video/webm'] }, }).use(Webcam) expect( - (uppy.getPlugin('Webcam') as Webcam).getMediaRecorderOptions() - .mimeType, + uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType, ).toEqual('video/webm') }) @@ -89,8 +84,7 @@ describe('Webcam', () => { preferredVideoMimeType: 'video/webm', }) expect( - (uppy.getPlugin('Webcam') as Webcam).getMediaRecorderOptions() - .mimeType, + uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType, ).toEqual('video/webm') }) @@ -104,8 +98,7 @@ describe('Webcam', () => { restrictions: { allowedFileTypes: ['video/mp4', 'video/webm'] }, }).use(Webcam) expect( - (uppy.getPlugin('Webcam') as Webcam).getMediaRecorderOptions() - .mimeType, + uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType, ).toEqual(undefined) }) }) diff --git a/packages/@uppy/webcam/src/Webcam.tsx b/packages/@uppy/webcam/src/Webcam.tsx index 25aa213cd..54ed86759 100644 --- a/packages/@uppy/webcam/src/Webcam.tsx +++ b/packages/@uppy/webcam/src/Webcam.tsx @@ -20,6 +20,12 @@ import locale from './locale.js' import PermissionsScreen from './PermissionsScreen.js' import supportsMediaRecorder from './supportsMediaRecorder.js' +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Webcam: Webcam + } +} + /** * Normalize a MIME type or file extension into a MIME type. * diff --git a/packages/@uppy/webdav/src/Webdav.tsx b/packages/@uppy/webdav/src/Webdav.tsx index 8b1842a79..acf6e12df 100644 --- a/packages/@uppy/webdav/src/Webdav.tsx +++ b/packages/@uppy/webdav/src/Webdav.tsx @@ -163,3 +163,9 @@ export default class Webdav return this.view.render(state) } } + +declare module '@uppy/core' { + export interface PluginTypeRegistry { + WebDav: Webdav + } +} diff --git a/packages/@uppy/xhr-upload/src/index.ts b/packages/@uppy/xhr-upload/src/index.ts index 539c24d32..f817181b0 100644 --- a/packages/@uppy/xhr-upload/src/index.ts +++ b/packages/@uppy/xhr-upload/src/index.ts @@ -86,6 +86,12 @@ declare module '@uppy/core' { } } +declare module '@uppy/core' { + export interface PluginTypeRegistry { + XHRUpload: XHRUpload + } +} + function buildResponseError( xhr?: XMLHttpRequest, err?: string | Error | NetworkError, diff --git a/packages/@uppy/zoom/src/Zoom.tsx b/packages/@uppy/zoom/src/Zoom.tsx index afe4277a0..8cbc8b0e2 100644 --- a/packages/@uppy/zoom/src/Zoom.tsx +++ b/packages/@uppy/zoom/src/Zoom.tsx @@ -107,3 +107,9 @@ export default class Zoom return this.view.render(state) } } + +declare module '@uppy/core' { + export interface PluginTypeRegistry { + Zoom: Zoom + } +}