From b27fe871c4bc1e0697fe5adeb73704581fbb0eae Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Tue, 14 Feb 2023 14:09:32 +0530 Subject: [PATCH] refactor: improve type checking for DispatchingStore dispatch payloads --- .../src/composables/settings.ts | 14 +++--- .../src/helpers/fb/settings.ts | 4 +- .../src/newstore/DispatchingStore.ts | 31 +++++++----- .../src/newstore/RESTSession.ts | 10 ++-- .../src/newstore/SocketIOSession.ts | 2 +- .../src/newstore/WebSocketSession.ts | 2 +- .../src/newstore/collections.ts | 10 +++- .../src/newstore/environments.ts | 3 +- .../hoppscotch-common/src/newstore/history.ts | 6 ++- .../src/newstore/localstate.ts | 12 +++-- .../src/newstore/settings.ts | 48 +++++++++++-------- 11 files changed, 85 insertions(+), 57 deletions(-) diff --git a/packages/hoppscotch-common/src/composables/settings.ts b/packages/hoppscotch-common/src/composables/settings.ts index d5eb3d4df..9aef1545a 100644 --- a/packages/hoppscotch-common/src/composables/settings.ts +++ b/packages/hoppscotch-common/src/composables/settings.ts @@ -1,15 +1,15 @@ import { Ref } from "vue" -import { settingsStore, SettingsType } from "~/newstore/settings" +import { settingsStore, SettingsDef } from "~/newstore/settings" import { pluck, distinctUntilChanged } from "rxjs/operators" import { useStream, useStreamStatic } from "./stream" -export function useSetting( +export function useSetting( settingKey: K -): Ref { +): Ref { return useStream( settingsStore.subject$.pipe(pluck(settingKey), distinctUntilChanged()), settingsStore.value[settingKey], - (value: SettingsType[K]) => { + (value: SettingsDef[K]) => { settingsStore.dispatch({ dispatcher: "applySetting", payload: { @@ -25,13 +25,13 @@ export function useSetting( * A static version (does not require component setup) * of `useSetting` */ -export function useSettingStatic( +export function useSettingStatic( settingKey: K -): [Ref, () => void] { +): [Ref, () => void] { return useStreamStatic( settingsStore.subject$.pipe(pluck(settingKey), distinctUntilChanged()), settingsStore.value[settingKey], - (value: SettingsType[K]) => { + (value: SettingsDef[K]) => { settingsStore.dispatch({ dispatcher: "applySetting", payload: { diff --git a/packages/hoppscotch-common/src/helpers/fb/settings.ts b/packages/hoppscotch-common/src/helpers/fb/settings.ts index a3796acc9..1afc5713d 100644 --- a/packages/hoppscotch-common/src/helpers/fb/settings.ts +++ b/packages/hoppscotch-common/src/helpers/fb/settings.ts @@ -6,7 +6,7 @@ import { setDoc, } from "firebase/firestore" import { platform } from "~/platform" -import { applySetting, settingsStore, SettingsType } from "~/newstore/settings" +import { applySetting, settingsStore, SettingsDef } from "~/newstore/settings" /** * Used locally to prevent infinite loop when settings sync update @@ -59,7 +59,7 @@ export function initSettings() { } else { writeSettings( dispatch.payload.settingKey, - settingsStore.value[dispatch.payload.settingKey as keyof SettingsType] + settingsStore.value[dispatch.payload.settingKey as keyof SettingsDef] ) } } diff --git a/packages/hoppscotch-common/src/newstore/DispatchingStore.ts b/packages/hoppscotch-common/src/newstore/DispatchingStore.ts index 81fdc1830..c6c71d788 100644 --- a/packages/hoppscotch-common/src/newstore/DispatchingStore.ts +++ b/packages/hoppscotch-common/src/newstore/DispatchingStore.ts @@ -2,9 +2,9 @@ import { Subject, BehaviorSubject } from "rxjs" import { map } from "rxjs/operators" import { assign, clone } from "lodash-es" -type dispatcherFunc = ( +type DispatcherFunc = ( currentVal: StoreType, - payload: any + payload: PayloadType ) => Partial /** @@ -13,26 +13,32 @@ type dispatcherFunc = ( * This function exists to provide better typing for dispatch function. * As you can see, its pretty much an identity function. */ -export const defineDispatchers = ( +export const defineDispatchers = < + StoreType, + T extends { [x: string]: DispatcherFunc } +>( // eslint-disable-next-line no-unused-vars - dispatchers: { [_ in keyof T]: dispatcherFunc } + dispatchers: T ) => dispatchers type Dispatch< StoreType, - DispatchersType extends Record> + DispatchersType extends { [x: string]: DispatcherFunc }, + Dispatcher extends keyof DispatchersType > = { - dispatcher: keyof DispatchersType - payload: any + dispatcher: Dispatcher + payload: Parameters[1] } export default class DispatchingStore< StoreType, - DispatchersType extends Record> + DispatchersType extends { [x: string]: DispatcherFunc } > { #state$: BehaviorSubject #dispatchers: DispatchersType - #dispatches$: Subject> = new Subject() + #dispatches$: Subject< + Dispatch + > = new Subject() constructor(initialValue: StoreType, dispatchers: DispatchersType) { this.#state$ = new BehaviorSubject(initialValue) @@ -64,9 +70,12 @@ export default class DispatchingStore< return this.#dispatches$ } - dispatch({ dispatcher, payload }: Dispatch) { + dispatch({ + dispatcher, + payload, + }: Dispatch) { if (!this.#dispatchers[dispatcher]) - throw new Error(`Undefined dispatch type '${dispatcher}'`) + throw new Error(`Undefined dispatch type '${String(dispatcher)}'`) this.#dispatches$.next({ dispatcher, payload }) } diff --git a/packages/hoppscotch-common/src/newstore/RESTSession.ts b/packages/hoppscotch-common/src/newstore/RESTSession.ts index a0b755778..f1e18cf29 100644 --- a/packages/hoppscotch-common/src/newstore/RESTSession.ts +++ b/packages/hoppscotch-common/src/newstore/RESTSession.ts @@ -114,7 +114,7 @@ const dispatchers = defineDispatchers({ }, } }, - deleteAllParams(curr: RESTSession) { + deleteAllParams(curr: RESTSession, {}) { return { request: { ...curr.request, @@ -168,7 +168,7 @@ const dispatchers = defineDispatchers({ }, } }, - deleteAllHeaders(curr: RESTSession) { + deleteAllHeaders(curr: RESTSession, {}) { return { request: { ...curr.request, @@ -257,7 +257,7 @@ const dispatchers = defineDispatchers({ }, } }, - deleteAllFormDataEntries(curr: RESTSession) { + deleteAllFormDataEntries(curr: RESTSession, {}) { // Only perform update if the current content-type is formdata if (curr.request.body.contentType !== "multipart/form-data") return {} @@ -288,7 +288,7 @@ const dispatchers = defineDispatchers({ } }, // eslint-disable-next-line @typescript-eslint/no-unused-vars - clearResponse(_curr: RESTSession) { + clearResponse(_curr: RESTSession, {}) { return { response: null, } @@ -490,7 +490,7 @@ export function setRESTTestScript(newScript: string) { }) } -export function setRESTReqBody(newBody: HoppRESTReqBody | null) { +export function setRESTReqBody(newBody: HoppRESTReqBody) { restSessionStore.dispatch({ dispatcher: "setRequestBody", payload: { diff --git a/packages/hoppscotch-common/src/newstore/SocketIOSession.ts b/packages/hoppscotch-common/src/newstore/SocketIOSession.ts index f8561bdef..43b020ed6 100644 --- a/packages/hoppscotch-common/src/newstore/SocketIOSession.ts +++ b/packages/hoppscotch-common/src/newstore/SocketIOSession.ts @@ -109,7 +109,7 @@ export function setSIOEndpoint(newEndpoint: string) { }) } -export function setSIOVersion(newVersion: string) { +export function setSIOVersion(newVersion: SIOClientVersion) { SIOSessionStore.dispatch({ dispatcher: "setVersion", payload: { diff --git a/packages/hoppscotch-common/src/newstore/WebSocketSession.ts b/packages/hoppscotch-common/src/newstore/WebSocketSession.ts index 6f5759e0a..9f9f0692d 100644 --- a/packages/hoppscotch-common/src/newstore/WebSocketSession.ts +++ b/packages/hoppscotch-common/src/newstore/WebSocketSession.ts @@ -74,7 +74,7 @@ const dispatchers = defineDispatchers({ }, } }, - deleteAllProtocols(curr: HoppWSSession) { + deleteAllProtocols(curr: HoppWSSession, {}) { return { request: { endpoint: curr.request.endpoint, diff --git a/packages/hoppscotch-common/src/newstore/collections.ts b/packages/hoppscotch-common/src/newstore/collections.ts index 3ed853234..6ccec0e66 100644 --- a/packages/hoppscotch-common/src/newstore/collections.ts +++ b/packages/hoppscotch-common/src/newstore/collections.ts @@ -127,7 +127,13 @@ const restCollectionDispatchers = defineDispatchers({ editFolder( { state }: RESTCollectionStoreType, - { path, folder }: { path: string; folder: string } + { + path, + folder, + }: { + path: string + folder: HoppCollection + } ) { const newState = state @@ -393,7 +399,7 @@ const gqlCollectionDispatchers = defineDispatchers({ editFolder( { state }: GraphqlCollectionStoreType, - { path, folder }: { path: string; folder: string } + { path, folder }: { path: string; folder: HoppCollection } ) { const newState = state diff --git a/packages/hoppscotch-common/src/newstore/environments.ts b/packages/hoppscotch-common/src/newstore/environments.ts index 54c7c63df..03dc71c9e 100644 --- a/packages/hoppscotch-common/src/newstore/environments.ts +++ b/packages/hoppscotch-common/src/newstore/environments.ts @@ -236,7 +236,8 @@ const dispatchers = defineDispatchers({ globals: entries, } }, - clearGlobalVariables() { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + clearGlobalVariables(_store, {}) { return { globals: [], } diff --git a/packages/hoppscotch-common/src/newstore/history.ts b/packages/hoppscotch-common/src/newstore/history.ts index 2b61edbc5..6943651c1 100644 --- a/packages/hoppscotch-common/src/newstore/history.ts +++ b/packages/hoppscotch-common/src/newstore/history.ts @@ -141,7 +141,8 @@ const RESTHistoryDispatchers = defineDispatchers({ state: currentVal.state.filter((e) => !isEqual(e, entry)), } }, - clearHistory() { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + clearHistory(_, {}) { return { state: [], } @@ -189,7 +190,8 @@ const GQLHistoryDispatchers = defineDispatchers({ state: currentVal.state.filter((e) => !isEqual(e, entry)), } }, - clearHistory() { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + clearHistory(_, {}) { return { state: [], } diff --git a/packages/hoppscotch-common/src/newstore/localstate.ts b/packages/hoppscotch-common/src/newstore/localstate.ts index b0ef621f7..1d78c46af 100644 --- a/packages/hoppscotch-common/src/newstore/localstate.ts +++ b/packages/hoppscotch-common/src/newstore/localstate.ts @@ -11,14 +11,18 @@ const defaultLocalState: LocalState = { REMEMBERED_TEAM_ID: undefined, } +type ApplyLocalState = { + [K in keyof LocalState]: { + key: K + value: LocalState[K] + } +}[keyof LocalState] + const dispatchers = defineDispatchers({ bulkApplyState(_currentState: LocalState, payload: Partial) { return payload }, - applyState( - _currentState: LocalState, - { key, value }: { key: K; value: LocalState[K] } - ) { + applyState(_currentState: LocalState, { key, value }: ApplyLocalState) { const result: Partial = { [key]: value, } diff --git a/packages/hoppscotch-common/src/newstore/settings.ts b/packages/hoppscotch-common/src/newstore/settings.ts index c3dfd33d4..cc4ffa43f 100644 --- a/packages/hoppscotch-common/src/newstore/settings.ts +++ b/packages/hoppscotch-common/src/newstore/settings.ts @@ -27,7 +27,7 @@ export const HoppFontSizes = ["small", "medium", "large"] as const export type HoppFontSize = (typeof HoppFontSizes)[number] -export type SettingsType = { +export type SettingsDef = { syncCollections: boolean syncHistory: boolean syncEnvironments: boolean @@ -53,7 +53,7 @@ export type SettingsType = { COLUMN_LAYOUT: boolean } -export const defaultSettings: SettingsType = { +export const defaultSettings: SettingsDef = { syncCollections: true, syncHistory: true, syncEnvironments: true, @@ -79,18 +79,22 @@ export const defaultSettings: SettingsType = { COLUMN_LAYOUT: true, } +type ApplySettingPayload = { + [K in keyof SettingsDef]: { + settingKey: K + value: SettingsDef[K] + } +}[keyof SettingsDef] + const validKeys = Object.keys(defaultSettings) const dispatchers = defineDispatchers({ - bulkApplySettings( - _currentState: SettingsType, - payload: Partial - ) { + bulkApplySettings(_currentState: SettingsDef, payload: Partial) { return payload }, toggleSetting( - currentState: SettingsType, - { settingKey }: { settingKey: KeysMatching } + currentState: SettingsDef, + { settingKey }: { settingKey: KeysMatching } ) { if (!has(currentState, settingKey)) { // console.log( @@ -99,14 +103,14 @@ const dispatchers = defineDispatchers({ return {} } - const result: Partial = {} + const result: Partial = {} result[settingKey] = !currentState[settingKey] return result }, - applySetting( - _currentState: SettingsType, - { settingKey, value }: { settingKey: K; value: SettingsType[K] } + applySetting( + _currentState: SettingsDef, + { settingKey, value }: ApplySettingPayload ) { if (!validKeys.includes(settingKey)) { // console.log( @@ -115,8 +119,9 @@ const dispatchers = defineDispatchers({ return {} } - const result: Partial = {} - result[settingKey] = value + const result: Partial = { + [settingKey]: value, + } return result }, @@ -129,20 +134,20 @@ export const settingsStore = new DispatchingStore(defaultSettings, dispatchers) */ export const settings$ = settingsStore.subject$.asObservable() -export function getSettingSubject( +export function getSettingSubject( settingKey: K -): Observable { +): Observable { return settingsStore.subject$.pipe(pluck(settingKey), distinctUntilChanged()) } -export function bulkApplySettings(settingsObj: Partial) { +export function bulkApplySettings(settingsObj: Partial) { settingsStore.dispatch({ dispatcher: "bulkApplySettings", payload: settingsObj, }) } -export function toggleSetting(settingKey: KeysMatching) { +export function toggleSetting(settingKey: KeysMatching) { settingsStore.dispatch({ dispatcher: "toggleSetting", payload: { @@ -151,12 +156,13 @@ export function toggleSetting(settingKey: KeysMatching) { }) } -export function applySetting( - settingKey: K, - value: SettingsType[K] +export function applySetting( + settingKey: K["settingKey"], + value: K["value"] ) { settingsStore.dispatch({ dispatcher: "applySetting", + // @ts-expect-error TS is not able to understand the type semantics here payload: { settingKey, value,