From d5387c5d468a826f071929395bcdedaa47cfff77 Mon Sep 17 00:00:00 2001 From: Jed Fox <git@jedfox.com> Date: Tue, 27 Jun 2023 13:02:58 -0400 Subject: [PATCH] Improve some of the typings in loot-core (#1180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit <!-- Thank you for submitting a pull request! Make sure to follow the instructions to write release notes for your PR — it should only take a minute or two: https://github.com/actualbudget/docs#writing-good-release-notes --> --- packages/desktop-client/package.json | 2 +- .../budget/rollover/BudgetSummary.tsx | 2 +- packages/loot-core/package.json | 2 +- .../loot-core/src/client/actions/account.ts | 1 - packages/loot-core/src/client/actions/app.ts | 2 +- packages/loot-core/src/client/actions/sync.ts | 9 +- packages/loot-core/src/mocks/budget.ts | 10 +- .../src/platform/server/connection/index.d.ts | 3 +- .../src/server/accounts/parse-file.test.ts | 3 +- .../src/server/accounts/parse-file.ts | 30 ++-- .../src/server/accounts/transactions.ts | 15 +- packages/loot-core/src/server/api.ts | 11 +- packages/loot-core/src/server/app.ts | 17 ++- packages/loot-core/src/server/backups.ts | 16 ++- packages/loot-core/src/server/budget/app.ts | 3 +- packages/loot-core/src/server/budget/base.ts | 2 +- .../src/server/budget/types/handlers.d.ts | 6 +- .../loot-core/src/server/cloud-storage.ts | 18 ++- packages/loot-core/src/server/main-app.ts | 3 +- packages/loot-core/src/server/main.ts | 9 +- packages/loot-core/src/server/mutators.ts | 9 +- packages/loot-core/src/server/notes/app.ts | 4 +- .../loot-core/src/server/schedules/app.ts | 11 +- .../src/server/schedules/types/handlers.ts | 24 ++-- packages/loot-core/src/server/sync/index.ts | 3 +- packages/loot-core/src/server/sync/reset.ts | 4 +- packages/loot-core/src/server/tools/app.ts | 4 +- .../src/server/tools/types/handlers.d.ts | 7 + packages/loot-core/src/server/undo.ts | 23 ++- packages/loot-core/src/shared/errors.ts | 10 +- packages/loot-core/src/shared/months.ts | 92 +++++++----- packages/loot-core/src/types/handlers.d.ts | 8 +- ...ain-handlers.d.ts => server-handlers.d.ts} | 134 +++++++++++------- packages/loot-core/src/types/util.d.ts | 1 + upcoming-release-notes/1180.md | 6 + yarn.lock | 12 +- 36 files changed, 330 insertions(+), 186 deletions(-) create mode 100644 packages/loot-core/src/server/tools/types/handlers.d.ts rename packages/loot-core/src/types/{main-handlers.d.ts => server-handlers.d.ts} (66%) create mode 100644 packages/loot-core/src/types/util.d.ts create mode 100644 upcoming-release-notes/1180.md diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index 3c331da7c..952c5dcc1 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -34,7 +34,7 @@ "inter-ui": "^3.19.3", "jest": "^27.0.0", "jest-watch-typeahead": "^2.2.2", - "memoize-one": "^4.0.0", + "memoize-one": "^6.0.0", "pikaday": "1.8.0", "react": "18.2.0", "react-app-rewired": "^2.2.1", diff --git a/packages/desktop-client/src/components/budget/rollover/BudgetSummary.tsx b/packages/desktop-client/src/components/budget/rollover/BudgetSummary.tsx index b4b1e6b5d..15afe636f 100644 --- a/packages/desktop-client/src/components/budget/rollover/BudgetSummary.tsx +++ b/packages/desktop-client/src/components/budget/rollover/BudgetSummary.tsx @@ -256,7 +256,7 @@ function ToBudget({ } type BudgetSummaryProps = { - month: string | number; + month: string; isGoalTemplatesEnabled: boolean; }; export function BudgetSummary({ diff --git a/packages/loot-core/package.json b/packages/loot-core/package.json index 2abe13dc7..14a1a1616 100644 --- a/packages/loot-core/package.json +++ b/packages/loot-core/package.json @@ -63,7 +63,7 @@ "jsverify": "^0.8.4", "lru-cache": "^5.1.1", "memfs": "3.1.1", - "memoize-one": "^4.0.0", + "memoize-one": "^6.0.0", "mockdate": "^3.0.5", "npm-run-all": "^4.1.3", "peggy": "3.0.2", diff --git a/packages/loot-core/src/client/actions/account.ts b/packages/loot-core/src/client/actions/account.ts index 6d9ef4350..e793d4936 100644 --- a/packages/loot-core/src/client/actions/account.ts +++ b/packages/loot-core/src/client/actions/account.ts @@ -198,7 +198,6 @@ export function importTransactions(id, transactions) { addNotification({ type: 'error', message: error.message, - internal: error.internal, }), ); }); diff --git a/packages/loot-core/src/client/actions/app.ts b/packages/loot-core/src/client/actions/app.ts index 1230fc76d..7ee582794 100644 --- a/packages/loot-core/src/client/actions/app.ts +++ b/packages/loot-core/src/client/actions/app.ts @@ -25,7 +25,7 @@ export function setLastUndoState(undoState) { // This is only used in the fake web version where everything runs in // the browser. It's a way to send a file to the backend to be // imported into the virtual filesystem. -export function uploadFile(filename, contents) { +export function uploadFile(filename: string, contents: ArrayBuffer) { return dispatch => { return send('upload-file-web', { filename, contents }); }; diff --git a/packages/loot-core/src/client/actions/sync.ts b/packages/loot-core/src/client/actions/sync.ts index 81ec013d3..bb5682cb6 100644 --- a/packages/loot-core/src/client/actions/sync.ts +++ b/packages/loot-core/src/client/actions/sync.ts @@ -13,7 +13,8 @@ export function resetSync() { alert(getUploadError(error)); if ( - (error.reason === 'encrypt-failure' && error.meta.isMissingKey) || + (error.reason === 'encrypt-failure' && + (error.meta as { isMissingKey?: boolean }).isMissingKey) || error.reason === 'file-has-new-key' ) { dispatch( @@ -38,8 +39,10 @@ export function sync() { return async (dispatch, getState) => { const prefs = getState().prefs.local; if (prefs && prefs.id) { - let { error } = await send('sync'); - return { error }; + let result = await send('sync'); + if ('error' in result) { + return { error: result.error }; + } } }; } diff --git a/packages/loot-core/src/mocks/budget.ts b/packages/loot-core/src/mocks/budget.ts index ba559dc74..56f7c894d 100644 --- a/packages/loot-core/src/mocks/budget.ts +++ b/packages/loot-core/src/mocks/budget.ts @@ -141,7 +141,7 @@ async function fillPrimaryChecking(handlers, account, payees, groups) { ); let currentDay = monthUtils.currentDay(); for (let month of months) { - let date = monthUtils.addDays(month, '12'); + let date = monthUtils.addDays(month, 12); if (monthUtils.isBefore(date, currentDay)) { transactions.push({ amount: -10000, @@ -152,7 +152,7 @@ async function fillPrimaryChecking(handlers, account, payees, groups) { }); } - date = monthUtils.addDays(month, '18'); + date = monthUtils.addDays(month, 18); if (monthUtils.isBefore(date, currentDay)) { transactions.push({ amount: -9000, @@ -163,7 +163,7 @@ async function fillPrimaryChecking(handlers, account, payees, groups) { }); } - date = monthUtils.addDays(month, '2'); + date = monthUtils.addDays(month, 2); if (monthUtils.isBefore(date, currentDay)) { transactions.push({ amount: -120000, @@ -174,7 +174,7 @@ async function fillPrimaryChecking(handlers, account, payees, groups) { }); } - date = monthUtils.addDays(month, '20'); + date = monthUtils.addDays(month, 20); if (monthUtils.isBefore(date, currentDay)) { transactions.push({ amount: -6000, @@ -186,7 +186,7 @@ async function fillPrimaryChecking(handlers, account, payees, groups) { }); } - date = monthUtils.addDays(month, '23'); + date = monthUtils.addDays(month, 23); if (monthUtils.isBefore(date, currentDay)) { transactions.push({ amount: -7500, diff --git a/packages/loot-core/src/platform/server/connection/index.d.ts b/packages/loot-core/src/platform/server/connection/index.d.ts index d9c8b557d..419e97262 100644 --- a/packages/loot-core/src/platform/server/connection/index.d.ts +++ b/packages/loot-core/src/platform/server/connection/index.d.ts @@ -1,8 +1,9 @@ +import type { Handlers } from '../../../types/handlers'; import type { ServerEvents } from '../../../types/server-events'; export function init( channel: Window | number, // in electron the port number, in web the worker - handlers: Record<string, () => void>, + handlers: Handlers, ): void; export type Init = typeof init; diff --git a/packages/loot-core/src/server/accounts/parse-file.test.ts b/packages/loot-core/src/server/accounts/parse-file.test.ts index b42076213..0c0c9a16a 100644 --- a/packages/loot-core/src/server/accounts/parse-file.test.ts +++ b/packages/loot-core/src/server/accounts/parse-file.test.ts @@ -39,7 +39,8 @@ async function importFileWithRealTime( global.restoreFakeDateNow(); if (transactions) { - transactions = transactions.map(trans => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transactions = (transactions as any[]).map(trans => ({ ...trans, amount: amountToInteger(trans.amount), date: dateFormat diff --git a/packages/loot-core/src/server/accounts/parse-file.ts b/packages/loot-core/src/server/accounts/parse-file.ts index 8819629fb..2922e1d2e 100644 --- a/packages/loot-core/src/server/accounts/parse-file.ts +++ b/packages/loot-core/src/server/accounts/parse-file.ts @@ -6,8 +6,17 @@ import { looselyParseAmount } from '../../shared/util'; import qif2json from './qif2json'; -export function parseFile(filepath, options?: unknown) { - let errors = []; +type ParseError = { message: string; internal: string }; +export type ParseFileResult = { + errors?: ParseError[]; + transactions?: unknown[]; +}; + +export async function parseFile( + filepath, + options?: unknown, +): Promise<ParseFileResult> { + let errors = Array<ParseError>(); let m = filepath.match(/\.[^.]*$/); if (m) { @@ -32,8 +41,11 @@ export function parseFile(filepath, options?: unknown) { return { errors, transactions: undefined }; } -async function parseCSV(filepath, options: { delimiter?: string } = {}) { - let errors = []; +async function parseCSV( + filepath, + options: { delimiter?: string } = {}, +): Promise<ParseFileResult> { + let errors = Array<ParseError>(); let contents = await fs.readFile(filepath); let data; @@ -59,8 +71,8 @@ async function parseCSV(filepath, options: { delimiter?: string } = {}) { return { errors, transactions: data }; } -async function parseQIF(filepath) { - let errors = []; +async function parseQIF(filepath): Promise<ParseFileResult> { + let errors = Array<ParseError>(); let contents = await fs.readFile(filepath); let data; @@ -75,7 +87,7 @@ async function parseQIF(filepath) { } return { - errors, + errors: [], transactions: data.transactions.map(trans => ({ amount: trans.amount != null ? looselyParseAmount(trans.amount) : null, date: trans.date, @@ -86,13 +98,13 @@ async function parseQIF(filepath) { }; } -async function parseOFX(filepath) { +async function parseOFX(filepath): Promise<ParseFileResult> { let { getOFXTransactions, initModule } = await import( /* webpackChunkName: 'xfo' */ 'node-libofx' ); await initModule(); - let errors = []; + let errors = Array<ParseError>(); let contents = await fs.readFile(filepath, 'binary'); let data; diff --git a/packages/loot-core/src/server/accounts/transactions.ts b/packages/loot-core/src/server/accounts/transactions.ts index 2775825eb..c57b29724 100644 --- a/packages/loot-core/src/server/accounts/transactions.ts +++ b/packages/loot-core/src/server/accounts/transactions.ts @@ -1,4 +1,5 @@ import * as connection from '../../platform/server/connection'; +import { TransactionEntity } from '../../types/models'; import * as db from '../db'; import { incrFetch, whereIn } from '../db/util'; import { batchMessages } from '../sync'; @@ -18,7 +19,9 @@ async function idsWithChildren(ids: string[]) { return [...set]; } -async function getTransactionsByIds(ids: string[]) { +async function getTransactionsByIds( + ids: string[], +): Promise<TransactionEntity[]> { // TODO: convert to whereIn // // or better yet, use ActualQL @@ -42,13 +45,13 @@ export async function batchUpdateTransactions({ deleted?: Array<{ id: string; payee: unknown }>; updated?: Array<{ id: string; - payee: unknown; - account: unknown; - category: unknown; + payee?: unknown; + account?: unknown; + category?: unknown; }>; learnCategories?: boolean; detectOrphanPayees?: boolean; -}) { +}): Promise<{ added: TransactionEntity[]; updated: unknown[] }> { // Track the ids of each type of transaction change (see below for why) let addedIds = []; let updatedIds = updated ? updated.map(u => u.id) : []; @@ -123,7 +126,7 @@ export async function batchUpdateTransactions({ // to the client so that can apply them. Note that added // transactions just return the full transaction. let resultAdded = allAdded; - let resultUpdated; + let resultUpdated: unknown[]; await batchMessages(async () => { await Promise.all(allAdded.map(t => transfer.onInsert(t))); diff --git a/packages/loot-core/src/server/api.ts b/packages/loot-core/src/server/api.ts index 85ad5183b..2d67d5e9c 100644 --- a/packages/loot-core/src/server/api.ts +++ b/packages/loot-core/src/server/api.ts @@ -14,6 +14,8 @@ import { deleteTransaction, } from '../shared/transactions'; import { integerToAmount } from '../shared/util'; +import { Handlers } from '../types/handlers'; +import { ServerHandlers } from '../types/server-handlers'; import { addTransactions } from './accounts/sync'; import { @@ -68,7 +70,7 @@ function withMutation(handler) { }; } -let handlers = {}; +let handlers = {} as unknown as Handlers; async function validateMonth(month) { if (!month.match(/^\d{4}-\d{2}$/)) { @@ -593,7 +595,8 @@ handlers['api/payee-delete'] = withMutation(async function ({ id }) { return handlers['payees-batch-change']({ deleted: [{ id }] }); }); -export default function installAPI(serverHandlers) { - handlers = Object.assign({}, serverHandlers, handlers); - return handlers; +export default function installAPI(serverHandlers: ServerHandlers) { + let merged = Object.assign({}, serverHandlers, handlers); + handlers = merged as Handlers; + return merged; } diff --git a/packages/loot-core/src/server/app.ts b/packages/loot-core/src/server/app.ts index c2bca043a..e52ffa294 100644 --- a/packages/loot-core/src/server/app.ts +++ b/packages/loot-core/src/server/app.ts @@ -7,20 +7,23 @@ import { captureException } from '../platform/exceptions'; // makes it cleaner to combine methods. We call a group of related // methods an "app". -class App { +class App<Handlers> { events; - handlers; + handlers: Handlers; services; unlistenServices; constructor() { - this.handlers = {}; + this.handlers = {} as Handlers; this.services = []; this.events = mitt(); this.unlistenServices = []; } - method(name, func) { + method<Name extends string & keyof Handlers>( + name: Name, + func: Handlers[Name], + ) { if (this.handlers[name] != null) { throw new Error( 'Conflicting method name, names must be globally unique: ' + name, @@ -36,7 +39,7 @@ class App { combine(...apps) { for (let app of apps) { Object.keys(app.handlers).forEach(name => { - this.method(name, app.handlers[name]); + this.method(name as string & keyof Handlers, app.handlers[name]); }); app.services.forEach(service => { @@ -72,6 +75,6 @@ class App { } } -export function createApp() { - return new App(); +export function createApp<T>() { + return new App<T>(); } diff --git a/packages/loot-core/src/server/backups.ts b/packages/loot-core/src/server/backups.ts index 2919b66df..69afbd4c7 100644 --- a/packages/loot-core/src/server/backups.ts +++ b/packages/loot-core/src/server/backups.ts @@ -14,7 +14,11 @@ import * as prefs from './prefs'; const LATEST_BACKUP_FILENAME = 'db.latest.sqlite'; let serviceInterval = null; -async function getBackups(id) { +export type Backup = { id: string; date: string } | LatestBackup; +type LatestBackup = { id: string; date: null; isLatest: true }; +type BackupWithDate = { id: string; date: Date }; + +async function getBackups(id: string): Promise<BackupWithDate[]> { const budgetDir = fs.getBudgetDir(id); const backupDir = fs.join(budgetDir, 'backups'); @@ -46,7 +50,7 @@ async function getBackups(id) { return backups; } -async function getLatestBackup(id) { +async function getLatestBackup(id: string): Promise<LatestBackup | null> { const budgetDir = fs.getBudgetDir(id); if (await fs.exists(fs.join(budgetDir, LATEST_BACKUP_FILENAME))) { return { @@ -58,7 +62,7 @@ async function getLatestBackup(id) { return null; } -export async function getAvailableBackups(id) { +export async function getAvailableBackups(id: string): Promise<Backup[]> { let backups = await getBackups(id); let latestBackup = await getLatestBackup(id); @@ -97,7 +101,7 @@ export async function updateBackups(backups) { return removed.concat(currentBackups.slice(10).map(backup => backup.id)); } -export async function makeBackup(id) { +export async function makeBackup(id: string) { const budgetDir = fs.getBudgetDir(id); // When making a backup, we no longer consider the user to be @@ -130,7 +134,7 @@ export async function makeBackup(id) { connection.send('backups-updated', await getAvailableBackups(id)); } -export async function loadBackup(id, backupId) { +export async function loadBackup(id: string, backupId: string) { const budgetDir = fs.getBudgetDir(id); if (!(await fs.exists(fs.join(budgetDir, LATEST_BACKUP_FILENAME)))) { @@ -203,7 +207,7 @@ export async function loadBackup(id, backupId) { } } -export function startBackupService(id) { +export function startBackupService(id: string) { if (serviceInterval) { clearInterval(serviceInterval); } diff --git a/packages/loot-core/src/server/budget/app.ts b/packages/loot-core/src/server/budget/app.ts index 6360e2a6d..db683c5df 100644 --- a/packages/loot-core/src/server/budget/app.ts +++ b/packages/loot-core/src/server/budget/app.ts @@ -5,8 +5,9 @@ import { undoable } from '../undo'; import * as actions from './actions'; import * as cleanupActions from './cleanup-template'; import * as goalActions from './goaltemplates'; +import { BudgetHandlers } from './types/handlers'; -let app = createApp(); +let app = createApp<BudgetHandlers>(); app.method('budget/budget-amount', mutator(undoable(actions.setBudget))); app.method( diff --git a/packages/loot-core/src/server/budget/base.ts b/packages/loot-core/src/server/budget/base.ts index 3489be3f2..6e4ba5b4f 100644 --- a/packages/loot-core/src/server/budget/base.ts +++ b/packages/loot-core/src/server/budget/base.ts @@ -14,7 +14,7 @@ export function getBudgetType() { return meta.budgetType || 'rollover'; } -export function getBudgetRange(start, end) { +export function getBudgetRange(start: string, end: string) { start = monthUtils.getMonth(start); end = monthUtils.getMonth(end); diff --git a/packages/loot-core/src/server/budget/types/handlers.d.ts b/packages/loot-core/src/server/budget/types/handlers.d.ts index bca907c64..d77978404 100644 --- a/packages/loot-core/src/server/budget/types/handlers.d.ts +++ b/packages/loot-core/src/server/budget/types/handlers.d.ts @@ -1,5 +1,9 @@ export interface BudgetHandlers { - 'budget/budget-amount': (...args: unknown[]) => Promise<unknown>; + 'budget/budget-amount': (arg: { + category: string /* category id */; + month: string; + amount: number; + }) => Promise<unknown>; 'budget/copy-previous-month': (...args: unknown[]) => Promise<unknown>; diff --git a/packages/loot-core/src/server/cloud-storage.ts b/packages/loot-core/src/server/cloud-storage.ts index f6829c1bc..7d61118b5 100644 --- a/packages/loot-core/src/server/cloud-storage.ts +++ b/packages/loot-core/src/server/cloud-storage.ts @@ -21,6 +21,15 @@ import { getServer } from './server-config'; let UPLOAD_FREQUENCY_IN_DAYS = 7; +export interface RemoteFile { + deleted: boolean; + fileId: string; + groupId: string; + name: string; + encryptKeyId: string; + hasKey: boolean; +} + async function checkHTTPStatus(res) { if (res.status !== 200) { return res.text().then(str => { @@ -37,7 +46,10 @@ async function fetchJSON(...args: Parameters<typeof fetch>) { return res.json(); } -export async function checkKey() { +export async function checkKey(): Promise<{ + valid: boolean; + error?: { reason: string }; +}> { let userToken = await asyncStorage.getItem('user-token'); let { cloudFileId, encryptKeyId } = prefs.getPrefs(); @@ -50,7 +62,7 @@ export async function checkKey() { }); } catch (e) { console.log(e); - return { error: { reason: 'network' } }; + return { valid: false, error: { reason: 'network' } }; } return { @@ -325,7 +337,7 @@ export async function removeFile(fileId) { }); } -export async function listRemoteFiles() { +export async function listRemoteFiles(): Promise<RemoteFile[] | null> { let userToken = await asyncStorage.getItem('user-token'); if (!userToken) { return null; diff --git a/packages/loot-core/src/server/main-app.ts b/packages/loot-core/src/server/main-app.ts index 6e89fc1ef..e884b2332 100644 --- a/packages/loot-core/src/server/main-app.ts +++ b/packages/loot-core/src/server/main-app.ts @@ -1,9 +1,10 @@ import * as connection from '../platform/server/connection'; +import { Handlers } from '../types/handlers'; import { createApp } from './app'; // Main app -const app = createApp(); +const app = createApp<Handlers>(); app.events.on('sync', info => { connection.send('sync-event', info); diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index c6553629f..1cf496e8e 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -17,6 +17,7 @@ import * as monthUtils from '../shared/months'; import q, { Query } from '../shared/query'; import { FIELD_TYPES as ruleFieldTypes } from '../shared/rules'; import { amountToInteger, stringToInteger } from '../shared/util'; +import { Handlers } from '../types/handlers'; import { exportToCSV, exportQueryToCSV } from './accounts/export-to-csv'; import * as link from './accounts/link'; @@ -84,7 +85,9 @@ function onSheetChange({ names }) { // handlers -export let handlers = {}; +// need to work around the type system here because the object +// is /currently/ empty but we promise to fill it in later +export let handlers = {} as unknown as Handlers; handlers['undo'] = mutator(async function () { return undo(); @@ -2264,7 +2267,7 @@ handlers['upload-file-web'] = async function ({ filename, contents }) { } await fs.writeFile('/uploads/' + filename, contents); - return 'ok'; + return {}; }; handlers['backups-get'] = async function ({ id }) { @@ -2300,7 +2303,7 @@ handlers['app-focused'] = async function () { } }; -handlers = installAPI(handlers); +handlers = installAPI(handlers) as Handlers; injectAPI.override((name, args) => runHandler(app.handlers[name], args)); diff --git a/packages/loot-core/src/server/mutators.ts b/packages/loot-core/src/server/mutators.ts index bd5035084..c23900b66 100644 --- a/packages/loot-core/src/server/mutators.ts +++ b/packages/loot-core/src/server/mutators.ts @@ -9,7 +9,9 @@ let globalMutationsEnabled = false; let _latestHandlerNames = []; -export function mutator(handler) { +export function mutator<T extends (...args: unknown[]) => unknown>( + handler: T, +): T { mutatingMethods.set(handler, true); return handler; } @@ -93,7 +95,10 @@ function _runMutator<T extends () => Promise<unknown>>( // Type cast needed as TS looses types over nested generic returns export const runMutator = sequential(_runMutator) as typeof _runMutator; -export function withMutatorContext(context, func) { +export function withMutatorContext<T>( + context: { undoListening: boolean; undoTag?: unknown }, + func: () => Promise<T>, +): Promise<T> { if (currentContext == null && !globalMutationsEnabled) { captureBreadcrumb('Recent methods: ' + _latestHandlerNames.join(', ')); captureException(new Error('withMutatorContext: mutator not running')); diff --git a/packages/loot-core/src/server/notes/app.ts b/packages/loot-core/src/server/notes/app.ts index ba6c9f7ca..bc76a5be1 100644 --- a/packages/loot-core/src/server/notes/app.ts +++ b/packages/loot-core/src/server/notes/app.ts @@ -1,7 +1,9 @@ import { createApp } from '../app'; import * as db from '../db'; -let app = createApp(); +import { NotesHandlers } from './types/handlers'; + +let app = createApp<NotesHandlers>(); app.method('notes-save', async ({ id, note }) => { await db.update('notes', { id, note }); diff --git a/packages/loot-core/src/server/schedules/app.ts b/packages/loot-core/src/server/schedules/app.ts index c2f3cfe07..fded91463 100644 --- a/packages/loot-core/src/server/schedules/app.ts +++ b/packages/loot-core/src/server/schedules/app.ts @@ -32,6 +32,7 @@ import { undoable } from '../undo'; import { Schedule as RSchedule } from '../util/rschedule'; import { findSchedules } from './find-schedules'; +import { SchedulesHandlers } from './types/handlers'; // Utilities @@ -357,11 +358,6 @@ async function skipNextDate({ id }) { }, }); } - -// `schedule` here might not be a saved schedule, so it might not have -// an id -function getPossibleTransactions({ schedule }) {} - function discoverSchedules() { return findSchedules(); } @@ -437,7 +433,7 @@ function onApplySync(oldValues, newValues) { // This is the service that move schedules forward automatically and // posts transactions -async function postTransactionForSchedule({ id }) { +async function postTransactionForSchedule({ id }: { id: string }) { let { data } = await aqlQuery(q('schedules').filter({ id }).select('*')); let schedule = data[0]; if (schedule == null || schedule._account == null) { @@ -533,7 +529,7 @@ async function advanceSchedulesService(syncSuccess) { } // Expose functions to the client -let app = createApp(); +let app = createApp<SchedulesHandlers>(); app.method('schedule/create', mutator(undoable(createSchedule))); app.method('schedule/update', mutator(undoable(updateSchedule))); @@ -547,7 +543,6 @@ app.method( 'schedule/force-run-service', mutator(() => advanceSchedulesService(true)), ); -app.method('schedule/get-possible-transactions', getPossibleTransactions); app.method('schedule/discover', discoverSchedules); app.method('schedule/get-upcoming-dates', getUpcomingDates); diff --git a/packages/loot-core/src/server/schedules/types/handlers.ts b/packages/loot-core/src/server/schedules/types/handlers.ts index 7f7ec60d6..0992b858a 100644 --- a/packages/loot-core/src/server/schedules/types/handlers.ts +++ b/packages/loot-core/src/server/schedules/types/handlers.ts @@ -1,19 +1,27 @@ export interface SchedulesHandlers { - 'schedule/create': () => Promise<unknown>; + 'schedule/create': (arg: { + schedule: unknown; + conditions: unknown[]; + }) => Promise<string>; - 'schedule/update': () => Promise<unknown>; + 'schedule/update': (schedule: { + schedule; + conditions?; + resetNextDate?: boolean; + }) => Promise<void>; - 'schedule/delete': () => Promise<unknown>; + 'schedule/delete': (arg: { id: string }) => Promise<void>; - 'schedule/skip-next-date': () => Promise<unknown>; + 'schedule/skip-next-date': (arg: { id: string }) => Promise<void>; - 'schedule/post-transaction': () => Promise<unknown>; + 'schedule/post-transaction': (arg: { id: string }) => Promise<void>; 'schedule/force-run-service': () => Promise<unknown>; - 'schedule/get-possible-transactions': () => Promise<unknown>; - 'schedule/discover': () => Promise<unknown>; - 'schedule/get-upcoming-dates': () => Promise<unknown>; + 'schedule/get-upcoming-dates': (arg: { + config; + count: number; + }) => Promise<string[]>; } diff --git a/packages/loot-core/src/server/sync/index.ts b/packages/loot-core/src/server/sync/index.ts index e41ed6088..d2ee55476 100644 --- a/packages/loot-core/src/server/sync/index.ts +++ b/packages/loot-core/src/server/sync/index.ts @@ -562,7 +562,8 @@ export async function initialFullSync(): Promise<void> { } export const fullSync = once(async function (): Promise< - { messages: Message[] } | { error: unknown } + | { messages: Message[] } + | { error: { message: string; reason: string; meta: unknown } } > { app.events.emit('sync', { type: 'start' }); let messages; diff --git a/packages/loot-core/src/server/sync/reset.ts b/packages/loot-core/src/server/sync/reset.ts index 27e18feb9..ae8627757 100644 --- a/packages/loot-core/src/server/sync/reset.ts +++ b/packages/loot-core/src/server/sync/reset.ts @@ -6,7 +6,9 @@ import * as db from '../db'; import { runMutator } from '../mutators'; import * as prefs from '../prefs'; -export default async function resetSync(keyState?) { +export default async function resetSync( + keyState?, +): Promise<{ error?: { reason: string; meta?: unknown } }> { if (!keyState) { // If we aren't resetting the key, make sure our key is up-to-date // so we don't accidentally upload a file encrypted with the wrong diff --git a/packages/loot-core/src/server/tools/app.ts b/packages/loot-core/src/server/tools/app.ts index 36d65470e..867ad2638 100644 --- a/packages/loot-core/src/server/tools/app.ts +++ b/packages/loot-core/src/server/tools/app.ts @@ -3,7 +3,9 @@ import { createApp } from '../app'; import * as db from '../db'; import { runMutator } from '../mutators'; -let app = createApp(); +import { ToolsHandlers } from './types/handlers'; + +let app = createApp<ToolsHandlers>(); app.method('tools/fix-split-transactions', async () => { // 1. Check for child transactions that have a blank payee, and set diff --git a/packages/loot-core/src/server/tools/types/handlers.d.ts b/packages/loot-core/src/server/tools/types/handlers.d.ts new file mode 100644 index 000000000..841db08c0 --- /dev/null +++ b/packages/loot-core/src/server/tools/types/handlers.d.ts @@ -0,0 +1,7 @@ +export interface ToolsHandlers { + 'tools/fix-split-transactions': () => Promise<{ + numBlankPayees: number; + numCleared: number; + numDeleted: number; + }>; +} diff --git a/packages/loot-core/src/server/undo.ts b/packages/loot-core/src/server/undo.ts index bfc67cc03..f21766309 100644 --- a/packages/loot-core/src/server/undo.ts +++ b/packages/loot-core/src/server/undo.ts @@ -7,11 +7,11 @@ import { withMutatorContext, getMutatorContext } from './mutators'; import { sendMessages } from './sync'; // A marker always sits as the first entry to simplify logic -type MarkerMessage = { type: 'marker'; meta? }; +type MarkerMessage = { type: 'marker'; meta?: unknown }; type MessagesMessage = { type: 'messages'; messages: unknown[]; - meta?; + meta?: unknown; oldData; undoTag; }; @@ -56,7 +56,10 @@ export function clearUndo() { CURSOR = 0; } -export function withUndo(func, meta?) { +export function withUndo<T>( + func: () => Promise<T>, + meta?: unknown, +): Promise<T> { let context = getMutatorContext(); if (context.undoDisabled || context.undoListening) { return func(); @@ -64,7 +67,7 @@ export function withUndo(func, meta?) { MESSAGE_HISTORY = MESSAGE_HISTORY.slice(0, CURSOR + 1); - let marker = { type: 'marker' as const, meta }; + let marker: MarkerMessage = { type: 'marker', meta }; if (MESSAGE_HISTORY[MESSAGE_HISTORY.length - 1].type === 'marker') { MESSAGE_HISTORY[MESSAGE_HISTORY.length - 1] = marker; @@ -79,8 +82,16 @@ export function withUndo(func, meta?) { ); } -export function undoable(func) { - return (...args) => { +// for some reason `void` is not inferred properly without this overload +export function undoable<Args extends unknown[]>( + func: (...args: Args) => Promise<void>, +): (...args: Args) => Promise<void>; +export function undoable< + Args extends unknown[], + Return extends Promise<unknown>, +>(func: (...args: Args) => Return): (...args: Args) => Return; +export function undoable(func: (...args: unknown[]) => Promise<unknown>) { + return (...args: unknown[]) => { return withUndo(() => { return func(...args); }); diff --git a/packages/loot-core/src/shared/errors.ts b/packages/loot-core/src/shared/errors.ts index 919c21002..61b16f4f1 100644 --- a/packages/loot-core/src/shared/errors.ts +++ b/packages/loot-core/src/shared/errors.ts @@ -1,9 +1,15 @@ -export function getUploadError({ reason, meta }) { +export function getUploadError({ + reason, + meta, +}: { + reason: string; + meta?: unknown; +}) { switch (reason) { case 'unauthorized': return 'You are not logged in.'; case 'encrypt-failure': - if (meta.isMissingKey) { + if ((meta as { isMissingKey: boolean }).isMissingKey) { return 'Encrypting your file failed because you are missing your encryption key. Create your key in the next step.'; } return 'Encrypting the file failed. You have the correct key so this is an internal bug. To fix this, generate a new key in the next step.'; diff --git a/packages/loot-core/src/shared/months.ts b/packages/loot-core/src/shared/months.ts index b37b93ec5..275f74965 100644 --- a/packages/loot-core/src/shared/months.ts +++ b/packages/loot-core/src/shared/months.ts @@ -1,7 +1,7 @@ import * as d from 'date-fns'; import memoizeOne from 'memoize-one'; -export function _parse(value: string | Date) { +export function _parse(value: string | number | Date) { if (typeof value === 'string') { // Dates are hard. We just want to deal with months in the format // 2020-01 and days in the format 2020-01-01, but life is never @@ -66,24 +66,27 @@ export function _parse(value: string | Date) { return new Date(parseInt(year), 0, 1, 12); } } + if (typeof value === 'number') { + return new Date(value); + } return value; } export const parseDate = _parse; -export function yearFromDate(date) { +export function yearFromDate(date: string | number | Date) { return d.format(_parse(date), 'yyyy'); } -export function monthFromDate(date) { +export function monthFromDate(date: string | number | Date) { return d.format(_parse(date), 'yyyy-MM'); } -export function dayFromDate(date) { +export function dayFromDate(date: string | number | Date) { return d.format(_parse(date), 'yyyy-MM-dd'); } -export function currentMonth() { +export function currentMonth(): string { if (global.IS_TESTING) { return global.currentMonth || '2017-01'; } else { @@ -99,57 +102,64 @@ export function currentDay() { } } -export function nextMonth(month) { +export function nextMonth(month: string | Date) { return d.format(d.addMonths(_parse(month), 1), 'yyyy-MM'); } -export function prevMonth(month) { +export function prevMonth(month: string | Date) { return d.format(d.subMonths(_parse(month), 1), 'yyyy-MM'); } -export function addMonths(month, n) { +export function addMonths(month: string | Date, n: number) { return d.format(d.addMonths(_parse(month), n), 'yyyy-MM'); } -export function addWeeks(date, n) { +export function addWeeks(date: string | Date, n: number) { return d.format(d.addWeeks(_parse(date), n), 'yyyy-MM-dd'); } -export function differenceInCalendarMonths(month1, month2) { +export function differenceInCalendarMonths( + month1: string | Date, + month2: string | Date, +) { return d.differenceInCalendarMonths(_parse(month1), _parse(month2)); } -export function subMonths(month, n) { +export function subMonths(month: string | Date, n: number) { return d.format(d.subMonths(_parse(month), n), 'yyyy-MM'); } -export function addDays(day, n) { +export function addDays(day: string | Date, n: number) { return d.format(d.addDays(_parse(day), n), 'yyyy-MM-dd'); } -export function subDays(day, n) { +export function subDays(day: string | Date, n: number) { return d.format(d.subDays(_parse(day), n), 'yyyy-MM-dd'); } -export function isBefore(month1, month2) { +export function isBefore(month1: string | Date, month2: string | Date) { return d.isBefore(_parse(month1), _parse(month2)); } -export function isAfter(month1, month2) { +export function isAfter(month1: string | Date, month2: string | Date) { return d.isAfter(_parse(month1), _parse(month2)); } // TODO: This doesn't really fit in this module anymore, should // probably live elsewhere -export function bounds(month) { +export function bounds(month: string | Date) { return { start: parseInt(d.format(d.startOfMonth(_parse(month)), 'yyyyMMdd')), end: parseInt(d.format(d.endOfMonth(_parse(month)), 'yyyyMMdd')), }; } -export function _range(start, end, inclusive = false) { - const months = []; +export function _range( + start: string | Date, + end: string | Date, + inclusive = false, +): string[] { + const months: string[] = []; let month = monthFromDate(start); while (d.isBefore(_parse(month), _parse(end))) { months.push(month); @@ -163,16 +173,20 @@ export function _range(start, end, inclusive = false) { return months; } -export function range(start, end) { +export function range(start: string, end: string) { return _range(start, end); } -export function rangeInclusive(start, end) { +export function rangeInclusive(start: string, end: string) { return _range(start, end, true); } -export function _dayRange(start, end, inclusive = false) { - const days = []; +export function _dayRange( + start: string, + end: string | Date, + inclusive = false, +): string[] { + const days: string[] = []; let day = start; while (d.isBefore(_parse(day), _parse(end))) { days.push(day); @@ -186,48 +200,48 @@ export function _dayRange(start, end, inclusive = false) { return days; } -export function dayRange(start, end) { +export function dayRange(start: string, end: string) { return _dayRange(start, end); } -export function dayRangeInclusive(start, end) { +export function dayRangeInclusive(start: string, end: string) { return _dayRange(start, end, true); } -export function getMonthIndex(month) { +export function getMonthIndex(month: string) { return parseInt(month.slice(5, 7)) - 1; } -export function getYear(month) { +export function getYear(month: string) { return month.slice(0, 4); } -export function getMonth(day) { +export function getMonth(day: string) { return day.slice(0, 7); } -export function getYearStart(month) { +export function getYearStart(month: string) { return getYear(month) + '-01'; } -export function getYearEnd(month) { +export function getYearEnd(month: string) { return getYear(month) + '-12'; } -export function sheetForMonth(month) { +export function sheetForMonth(month: string) { return 'budget' + month.replace('-', ''); } -export function nameForMonth(month) { +export function nameForMonth(month: string | Date) { // eslint-disable-next-line rulesdir/typography return d.format(_parse(month), "MMMM 'yy"); } -export function format(month, str) { +export function format(month: string | Date, str: string) { return d.format(_parse(month), str); } -export const getDateFormatRegex = memoizeOne(format => { +export const getDateFormatRegex = memoizeOne((format: string) => { return new RegExp( format .replace(/d+/g, '\\d{1,2}') @@ -236,14 +250,14 @@ export const getDateFormatRegex = memoizeOne(format => { ); }); -export const getDayMonthFormat = memoizeOne(format => { +export const getDayMonthFormat = memoizeOne((format: string) => { return format .replace(/y+/g, '') .replace(/[^\w]$/, '') .replace(/^[^\w]/, ''); }); -export const getDayMonthRegex = memoizeOne(format => { +export const getDayMonthRegex = memoizeOne((format: string) => { let regex = format .replace(/y+/g, '') .replace(/[^\w]$/, '') @@ -253,7 +267,7 @@ export const getDayMonthRegex = memoizeOne(format => { return new RegExp('^' + regex + '$'); }); -export const getMonthYearFormat = memoizeOne(format => { +export const getMonthYearFormat = memoizeOne((format: string) => { return format .replace(/d+/g, '') .replace(/[^\w]$/, '') @@ -263,7 +277,7 @@ export const getMonthYearFormat = memoizeOne(format => { .replace(/--/, '-'); }); -export const getMonthYearRegex = memoizeOne(format => { +export const getMonthYearRegex = memoizeOne((format: string) => { let regex = format .replace(/d+/g, '') .replace(/[^\w]$/, '') @@ -274,11 +288,11 @@ export const getMonthYearRegex = memoizeOne(format => { return new RegExp('^' + regex + '$'); }); -export const getShortYearFormat = memoizeOne(format => { +export const getShortYearFormat = memoizeOne((format: string) => { return format.replace(/y+/g, 'yy'); }); -export const getShortYearRegex = memoizeOne(format => { +export const getShortYearRegex = memoizeOne((format: string) => { let regex = format .replace(/[^\w]$/, '') .replace(/^[^\w]/, '') diff --git a/packages/loot-core/src/types/handlers.d.ts b/packages/loot-core/src/types/handlers.d.ts index d77ae3613..81dd0f2c1 100644 --- a/packages/loot-core/src/types/handlers.d.ts +++ b/packages/loot-core/src/types/handlers.d.ts @@ -1,13 +1,15 @@ import type { BudgetHandlers } from '../server/budget/types/handlers'; import type { NotesHandlers } from '../server/notes/types/handlers'; import type { SchedulesHandlers } from '../server/schedules/types/handlers'; +import type { ToolsHandlers } from '../server/tools/types/handlers'; import type { ApiHandlers } from './api-handlers'; -import type { MainHandlers } from './main-handlers'; +import type { ServerHandlers } from './server-handlers'; export interface Handlers - extends MainHandlers, + extends ServerHandlers, ApiHandlers, BudgetHandlers, NotesHandlers, - SchedulesHandlers {} + SchedulesHandlers, + ToolsHandlers {} diff --git a/packages/loot-core/src/types/main-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts similarity index 66% rename from packages/loot-core/src/types/main-handlers.d.ts rename to packages/loot-core/src/types/server-handlers.d.ts index 3925c8d8d..061e40353 100644 --- a/packages/loot-core/src/types/main-handlers.d.ts +++ b/packages/loot-core/src/types/server-handlers.d.ts @@ -1,30 +1,39 @@ -export interface MainHandlers { - 'transaction-update': ( - transaction: unknown, - ) => Promise<Record<string, never>>; +import { ParseFileResult } from '../server/accounts/parse-file'; +import { batchUpdateTransactions } from '../server/accounts/transactions'; +import { Backup } from '../server/backups'; +import { RemoteFile } from '../server/cloud-storage'; +import { Message } from '../server/sync'; - undo: () => Promise<unknown>; +import { EmptyObject } from './util'; - redo: () => Promise<unknown>; +export interface ServerHandlers { + 'transaction-update': (transaction: { id: string }) => Promise<EmptyObject>; - 'transactions-batch-update': (arg: { - added; - deleted; - updated; - learnCategories; - }) => Promise<unknown>; + undo: () => Promise<void>; + + redo: () => Promise<void>; + + 'transactions-batch-update': ( + arg: Omit< + Parameters<typeof batchUpdateTransactions>[0], + 'detectOrphanPayees' + >, + ) => Promise<Awaited<ReturnType<typeof batchUpdateTransactions>>['updated']>; - 'transaction-add': (transaction) => Promise<Record<string, never>>; + 'transaction-add': (transaction) => Promise<EmptyObject>; - 'transaction-aupdatedd': (transaction) => Promise<Record<string, never>>; + 'transaction-add': (transaction) => Promise<EmptyObject>; - 'transaction-delete': (transaction) => Promise<Record<string, never>>; + 'transaction-delete': (transaction) => Promise<EmptyObject>; - 'transactions-parse-file': (arg: { filepath; options }) => Promise<unknown>; + 'transactions-parse-file': (arg: { + filepath: string; + options; + }) => Promise<ParseFileResult>; 'transactions-export': (arg: { transactions; - accounts; + accounts?; categoryGroups; payees; }) => Promise<unknown>; @@ -32,13 +41,13 @@ export interface MainHandlers { 'transactions-export-query': (arg: { query: queryState }) => Promise<unknown>; 'get-categories': () => Promise<{ - grouped: unknown; - list: unknown; + grouped: unknown[]; + list: unknown[]; }>; 'get-earliest-transaction': () => Promise<unknown>; - 'get-budget-bounds': () => Promise<unknown>; + 'get-budget-bounds': () => Promise<{ start: string; end: string }>; 'rollover-budget-month': (arg: { month }) => Promise<unknown>; @@ -52,7 +61,7 @@ export interface MainHandlers { 'category-move': (arg: { id; groupId; targetId }) => Promise<unknown>; - 'category-delete': (arg: { id; transferId }) => Promise<{ error }>; + 'category-delete': (arg: { id; transferId }) => Promise<{ error?: string }>; 'category-group-create': (arg: { name; @@ -69,13 +78,17 @@ export interface MainHandlers { 'payee-create': (arg: { name }) => Promise<unknown>; - 'payees-get': () => Promise<unknown>; + 'payees-get': () => Promise<unknown[]>; 'payees-get-rule-counts': () => Promise<unknown>; 'payees-merge': (arg: { targetId; mergeIds }) => Promise<unknown>; - 'payees-batch-change': (arg: { added; deleted; updated }) => Promise<unknown>; + 'payees-batch-change': (arg: { + added?; + deleted?; + updated?; + }) => Promise<unknown>; 'payees-check-orphaned': (arg: { ids }) => Promise<unknown>; @@ -163,9 +176,9 @@ export interface MainHandlers { 'account-close': (arg: { id; - transferAccountId; - categoryId; - forced; + transferAccountId?; + categoryId?; + forced?; }) => Promise<unknown>; 'account-reopen': (arg: { id }) => Promise<unknown>; @@ -184,12 +197,12 @@ export interface MainHandlers { }>; 'secret-set': (arg: { name: string; value: string }) => Promise<null>; - 'secret-check': (arg: string) => Promise<null>; + 'secret-check': (arg: string) => Promise<string | { error?: string }>; 'nordigen-poll-web-token': (arg: { upgradingAccountId; requisitionId; - }) => Promise<null>; + }) => Promise<{ error } | { data }>; 'nordigen-status': () => Promise<{ configured: boolean }>; @@ -211,7 +224,7 @@ export interface MainHandlers { }>; 'transactions-import': (arg: { accountId; transactions }) => Promise<{ - errors; + errors?: { message: string }[]; added; updated; }>; @@ -239,13 +252,16 @@ export interface MainHandlers { 'load-prefs': () => Promise<Record<string, unknown> | null>; - 'sync-reset': () => Promise<{ error }>; + 'sync-reset': () => Promise<{ error?: { reason: string; meta?: unknown } }>; 'sync-repair': () => Promise<unknown>; 'key-make': (arg: { password }) => Promise<unknown>; - 'key-test': (arg: { fileId; password }) => Promise<unknown>; + 'key-test': (arg: { + fileId; + password; + }) => Promise<{ error?: { reason: string } }>; 'get-did-bootstrap': () => Promise<boolean>; @@ -255,37 +271,47 @@ export interface MainHandlers { { error: string } | { bootstrapped: unknown; hasServer: boolean } >; - 'subscribe-bootstrap': (arg: { password }) => Promise<{ error: string }>; + 'subscribe-bootstrap': (arg: { password }) => Promise<{ error?: string }>; 'subscribe-get-user': () => Promise<{ offline: boolean } | null>; 'subscribe-change-password': (arg: { password; - }) => Promise<{ error: string }>; + }) => Promise<{ error?: string }>; - 'subscribe-sign-in': (arg: { password }) => Promise<{ error: string }>; + 'subscribe-sign-in': (arg: { password }) => Promise<{ error?: string }>; 'subscribe-sign-out': () => Promise<'ok'>; - 'get-server-version': () => Promise<{ error: string } | { version: string }>; + 'get-server-version': () => Promise<{ error?: string } | { version: string }>; 'get-server-url': () => Promise<unknown>; 'set-server-url': (arg: { url; validate }) => Promise<unknown>; - sync: () => Promise<{ error: string }>; + sync: () => Promise< + | { error: { message: string; reason: string; meta: unknown } } + | { messages: Message[] } + >; - 'get-budgets': () => Promise<unknown>; + 'get-budgets': () => Promise< + { + id: string; + cloudFileId: string; + groupId: string; + name: string; + }[] + >; - 'get-remote-files': () => Promise<unknown>; + 'get-remote-files': () => Promise<RemoteFile[]>; 'reset-budget-cache': () => Promise<unknown>; - 'upload-budget': (arg: { id } = {}) => Promise<{ error }>; + 'upload-budget': (arg: { id } = {}) => Promise<{ error?: string }>; - 'download-budget': (arg: { fileId; replace }) => Promise<{ error; id }>; + 'download-budget': (arg: { fileId; replace? }) => Promise<{ error; id }>; - 'sync-budget': () => Promise<Record<string, never>>; + 'sync-budget': () => Promise<EmptyObject>; 'load-budget': (arg: { id }) => Promise<{ error }>; @@ -293,28 +319,34 @@ export interface MainHandlers { 'close-budget': () => Promise<'ok'>; - 'delete-budget': (arg: { id; cloudFileId }) => Promise<'ok'>; + 'delete-budget': (arg: { id; cloudFileId? }) => Promise<'ok'>; 'create-budget': (arg: { budgetName?; avoidUpload?; - testMode: boolean; + testMode?: boolean; testBudgetId?; }) => Promise<unknown>; - 'import-budget': (arg: { filepath; type }) => Promise<{ error }>; + 'import-budget': (arg: { + filepath: string; + type: 'ynab4' | 'ynab5' | 'actual'; + }) => Promise<{ error?: string }>; - 'export-budget': () => Promise<unknown>; + 'export-budget': () => Promise<Buffer | null>; - 'upload-file-web': (arg: { filename; contents }) => Promise<'ok'>; + 'upload-file-web': (arg: { + filename: string; + contents: ArrayBuffer; + }) => Promise<EmptyObject | null>; - 'backups-get': (arg: { id }) => Promise<unknown>; + 'backups-get': (arg: { id: string }) => Promise<Backup[]>; - 'backup-load': (arg: { id; backupId }) => Promise<unknown>; + 'backup-load': (arg: { id: string; backupId: string }) => Promise<void>; - 'backup-make': (arg: { id }) => Promise<unknown>; + 'backup-make': (arg: { id: string }) => Promise<void>; - 'get-last-opened-backup': () => Promise<unknown>; + 'get-last-opened-backup': () => Promise<string | null>; - 'app-focused': () => Promise<unknown>; + 'app-focused': () => Promise<void>; } diff --git a/packages/loot-core/src/types/util.d.ts b/packages/loot-core/src/types/util.d.ts new file mode 100644 index 000000000..253e96456 --- /dev/null +++ b/packages/loot-core/src/types/util.d.ts @@ -0,0 +1 @@ +export type EmptyObject = Record<string, never>; diff --git a/upcoming-release-notes/1180.md b/upcoming-release-notes/1180.md new file mode 100644 index 000000000..f7c95d976 --- /dev/null +++ b/upcoming-release-notes/1180.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [j-f1] +--- + +Improve TypeScript types in `loot-core` diff --git a/yarn.lock b/yarn.lock index 337413a52..e846cfcd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -97,7 +97,7 @@ __metadata: inter-ui: ^3.19.3 jest: ^27.0.0 jest-watch-typeahead: ^2.2.2 - memoize-one: ^4.0.0 + memoize-one: ^6.0.0 pikaday: 1.8.0 react: 18.2.0 react-app-rewired: ^2.2.1 @@ -12357,7 +12357,7 @@ __metadata: lru-cache: ^5.1.1 md5: ^2.3.0 memfs: 3.1.1 - memoize-one: ^4.0.0 + memoize-one: ^6.0.0 mitt: ^3.0.0 mockdate: ^3.0.5 node-fetch: ^2.6.9 @@ -12579,10 +12579,10 @@ __metadata: languageName: node linkType: hard -"memoize-one@npm:^4.0.0": - version: 4.0.3 - resolution: "memoize-one@npm:4.0.3" - checksum: addd18c046542f57440ba70bf8ebd48663d17626cade681f777522ef70900a87ec72c5041bed8ece4f6d40a2cb58803bae388b50a4b740d64f36bcda20c147b7 +"memoize-one@npm:^6.0.0": + version: 6.0.0 + resolution: "memoize-one@npm:6.0.0" + checksum: f185ea69f7cceae5d1cb596266dcffccf545e8e7b4106ec6aa93b71ab9d16460dd118ac8b12982c55f6d6322fcc1485de139df07eacffaae94888b9b3ad7675f languageName: node linkType: hard -- GitLab