Update server handlers to use the implementation function type instead of duplicating the function arguments/return types (#4650)

* Update server handlers types to be more strict

* Release notes
This commit is contained in:
Joel Jeremy Marquez
2025-03-18 06:43:15 -07:00
committed by GitHub
parent b5cbaa52b2
commit 602b84342b
25 changed files with 282 additions and 335 deletions

View File

@@ -45,12 +45,12 @@ export const UserAccessRow = memo(
handleError(error);
}
} else {
const { someDeletionsFailed } = await send('access-delete-all', {
const result = await send('access-delete-all', {
fileId: cloudFileId as string,
ids: [access.userId],
});
if (someDeletionsFailed) {
if ('someDeletionsFailed' in result && result.someDeletionsFailed) {
dispatch(
addNotification({
notification: {

View File

@@ -85,6 +85,10 @@ export function MergeUnusedPayeesModal({
if (ruleId) {
const rule = await send('rule-get', { id: ruleId });
if (!rule) {
return;
}
dispatch(
replaceModal({ modal: { name: 'edit-rule', options: { rule } } }),
);

View File

@@ -534,6 +534,10 @@ export function ScheduleDetails({ id, transaction }: ScheduleDetailsProps) {
async function onEditRule(id: string) {
const rule = await send('rule-get', { id });
if (!rule) {
return;
}
globalDispatch(
pushModal({
modal: {

View File

@@ -1,16 +1,67 @@
// @ts-strict-ignore
import * as asyncStorage from '../../platform/server/asyncStorage';
import { UserAvailable, UserEntity } from '../../types/models/user';
import {
UserAvailable,
UserEntity,
NewUserAccessEntity,
} from '../../types/models';
import { createApp } from '../app';
import { del, get, patch, post } from '../post';
import { getServer } from '../server-config';
import { AdminHandlers } from './types/handlers';
export type AdminHandlers = {
'users-get': typeof getUsers;
'user-delete-all': typeof deleteAllUsers;
'user-add': typeof addUser;
'user-update': typeof updateUser;
'access-add': typeof addAccess;
'access-delete-all': typeof deleteAllAccess;
'access-get-available-users': typeof accessGetAvailableUsers;
'transfer-ownership': typeof transferOwnership;
'owner-created': typeof ownerCreated;
};
// Expose functions to the client
export const app = createApp<AdminHandlers>();
app.method('user-delete-all', async function (ids) {
app.method('users-get', getUsers);
app.method('user-delete-all', deleteAllUsers);
app.method('user-add', addUser);
app.method('user-update', updateUser);
app.method('access-add', addAccess);
app.method('access-delete-all', deleteAllAccess);
app.method('access-get-available-users', accessGetAvailableUsers);
app.method('transfer-ownership', transferOwnership);
app.method('owner-created', ownerCreated);
async function getUsers() {
const userToken = await asyncStorage.getItem('user-token');
if (userToken) {
const res = await get(getServer().BASE_SERVER + '/admin/users/', {
headers: {
'X-ACTUAL-TOKEN': userToken,
},
});
if (res) {
try {
const list = JSON.parse(res) as UserEntity[];
return list;
} catch (err) {
return { error: 'Failed to parse response: ' + err.message };
}
}
}
return null;
}
async function deleteAllUsers(
ids: Array<UserEntity['id']>,
): Promise<
{ someDeletionsFailed: boolean; ids?: number[] } | { error: string }
> {
const userToken = await asyncStorage.getItem('user-token');
if (userToken) {
try {
@@ -33,32 +84,11 @@ app.method('user-delete-all', async function (ids) {
}
return { someDeletionsFailed: true };
});
}
app.method('users-get', async function () {
const userToken = await asyncStorage.getItem('user-token');
if (userToken) {
const res = await get(getServer().BASE_SERVER + '/admin/users/', {
headers: {
'X-ACTUAL-TOKEN': userToken,
},
});
if (res) {
try {
const list = JSON.parse(res) as UserEntity[];
return list;
} catch (err) {
return { error: 'Failed to parse response: ' + err.message };
}
}
}
return null;
});
app.method('user-add', async function (user) {
async function addUser(
user: Omit<UserEntity, 'id'>,
): Promise<{ error: string } | { id: string }> {
const userToken = await asyncStorage.getItem('user-token');
if (userToken) {
@@ -74,9 +104,11 @@ app.method('user-add', async function (user) {
}
return null;
});
}
app.method('user-update', async function (user) {
async function updateUser(
user: Omit<UserEntity, 'id'>,
): Promise<{ error: string } | { id: string }> {
const userToken = await asyncStorage.getItem('user-token');
if (userToken) {
@@ -92,9 +124,11 @@ app.method('user-update', async function (user) {
}
return null;
});
}
app.method('access-add', async function (access) {
async function addAccess(
access: NewUserAccessEntity,
): Promise<{ error?: string } | Record<string, never>> {
const userToken = await asyncStorage.getItem('user-token');
if (userToken) {
@@ -110,9 +144,17 @@ app.method('access-add', async function (access) {
}
return null;
});
}
app.method('access-delete-all', async function ({ fileId, ids }) {
async function deleteAllAccess({
fileId,
ids,
}: {
fileId: string;
ids: string[];
}): Promise<
{ someDeletionsFailed: boolean; ids?: number[] } | { error: unknown }
> {
const userToken = await asyncStorage.getItem('user-token');
if (userToken) {
try {
@@ -133,9 +175,11 @@ app.method('access-delete-all', async function ({ fileId, ids }) {
}
return { someDeletionsFailed: true };
});
}
app.method('access-get-available-users', async function (fileId) {
async function accessGetAvailableUsers(
fileId: string,
): Promise<UserAvailable[] | { error: string }> {
const userToken = await asyncStorage.getItem('user-token');
if (userToken) {
@@ -158,9 +202,15 @@ app.method('access-get-available-users', async function (fileId) {
}
return [];
});
}
app.method('transfer-ownership', async function ({ fileId, newUserId }) {
async function transferOwnership({
fileId,
newUserId,
}: {
fileId: string;
newUserId: string;
}): Promise<{ error?: string } | Record<string, never>> {
const userToken = await asyncStorage.getItem('user-token');
if (userToken) {
@@ -178,9 +228,9 @@ app.method('transfer-ownership', async function ({ fileId, newUserId }) {
}
return {};
});
}
app.method('owner-created', async function () {
async function ownerCreated() {
const res = await get(getServer().BASE_SERVER + '/admin/owner-created/');
if (res) {
@@ -188,4 +238,4 @@ app.method('owner-created', async function () {
}
return null;
});
}

View File

@@ -1,46 +0,0 @@
import { UserAvailable, UserEntity } from '../../../types/models/user';
import { NewUserAccessEntity } from '../../../types/models/userAccess';
export interface AdminHandlers {
'users-get': () => Promise<UserEntity[] | null | { error: string }>;
'user-delete-all': (
ids: string[],
) => Promise<
{ someDeletionsFailed: boolean; ids?: number[] } | { error: string }
>;
'user-add': (
user: Omit<UserEntity, 'id'>,
) => Promise<{ error: string } | { id: string }>;
'user-update': (
user: Omit<UserEntity, 'id'>,
) => Promise<{ error: string } | { id: string }>;
'access-add': (
user: NewUserAccessEntity,
) => Promise<{ error?: string } | Record<string, never>>;
'access-delete-all': ({
fileId,
ids,
}: {
fileId: string;
ids: string[];
}) => Promise<{ someDeletionsFailed: boolean; ids?: number[] }>;
'access-get-available-users': (
fileId: string,
) => Promise<UserAvailable[] | { error: string }>;
'transfer-ownership': ({
fileId,
newUserId,
}: {
fileId: string;
newUserId: string;
}) => Promise<{ error?: string } | Record<string, never>>;
'owner-created': () => Promise<boolean>;
}

View File

@@ -21,8 +21,6 @@ import { reportModel } from '../reports/app';
import { batchMessages } from '../sync';
import { undoable } from '../undo';
import { DashboardHandlers } from './types/handlers';
function isExportedCustomReportWidget(
widget: ExportImportDashboardWidget,
): widget is ExportImportCustomReportWidget {
@@ -245,6 +243,15 @@ async function importDashboard({ filepath }: { filepath: string }) {
}
}
export type DashboardHandlers = {
'dashboard-update': typeof updateDashboard;
'dashboard-update-widget': typeof updateDashboardWidget;
'dashboard-reset': typeof resetDashboard;
'dashboard-add-widget': typeof addDashboardWidget;
'dashboard-remove-widget': typeof removeDashboardWidget;
'dashboard-import': typeof importDashboard;
};
export const app = createApp<DashboardHandlers>();
app.method('dashboard-update', mutator(undoable(updateDashboard)));

View File

@@ -1,24 +0,0 @@
import { type Widget } from '../../../types/models';
import { type EverythingButIdOptional } from '../../../types/util';
export interface DashboardHandlers {
'dashboard-update': (
widgets: EverythingButIdOptional<Omit<Widget, 'tombstone'>>[],
) => Promise<void>;
'dashboard-update-widget': (
widget: EverythingButIdOptional<Omit<Widget, 'tombstone'>>,
) => Promise<void>;
'dashboard-reset': () => Promise<void>;
'dashboard-add-widget': (
widget: Omit<Widget, 'id' | 'x' | 'y' | 'tombstone'> &
Partial<Pick<Widget, 'x' | 'y'>>,
) => Promise<void>;
'dashboard-remove-widget': (widgetId: string) => Promise<void>;
'dashboard-import': (args: {
filepath: string;
}) => Promise<
| { status: 'ok' }
| { error: 'json-parse-error' | 'internal-error' }
| { error: 'validation-error'; message: string }
>;
}

View File

@@ -1,6 +1,7 @@
// @ts-strict-ignore
import { v4 as uuidv4 } from 'uuid';
import { TransactionFilterEntity } from '../../types/models';
import { createApp } from '../app';
import * as db from '../db';
import { requiredFields } from '../models';
@@ -8,8 +9,6 @@ import { mutator } from '../mutators';
import { parseConditionsOrActions } from '../transactions/transaction-rules';
import { undoable } from '../undo';
import { FiltersHandlers } from './types/handlers';
const filterModel = {
validate(filter, { update }: { update?: boolean } = {}) {
requiredFields('transaction_filters', filter, ['conditions'], update);
@@ -110,7 +109,7 @@ function filterOptionsMatch(options1, options2) {
return keys1.every(key => opt1[key] === opt2[key]);
}
async function createFilter(filter) {
async function createFilter(filter): Promise<TransactionFilterEntity['id']> {
const filterId = uuidv4();
const item = {
id: filterId,
@@ -175,10 +174,16 @@ async function updateFilter(filter) {
await db.updateWithSchema('transaction_filters', filterModel.fromJS(item));
}
async function deleteFilter(id) {
async function deleteFilter(id: TransactionFilterEntity['id']) {
await db.delete_('transaction_filters', id);
}
export type FiltersHandlers = {
'filter-create': typeof createFilter;
'filter-update': typeof updateFilter;
'filter-delete': typeof deleteFilter;
};
export const app = createApp<FiltersHandlers>();
app.method('filter-create', mutator(createFilter));

View File

@@ -1,7 +0,0 @@
export interface FiltersHandlers {
'filter-create': (filter: object) => Promise<string>;
'filter-update': (filter: object) => Promise<void>;
'filter-delete': (id: string) => Promise<void>;
}

View File

@@ -2,12 +2,13 @@ import { NoteEntity } from '../../types/models';
import { createApp } from '../app';
import * as db from '../db';
import { NotesHandlers } from './types/handlers';
export type NotesHandlers = {
'notes-save': typeof updateNotes;
};
export const app = createApp<NotesHandlers>();
app.method('notes-save', updateNotes);
async function updateNotes({ id, note }: NoteEntity) {
await db.update('notes', { id, note });
}
app.method('notes-save', updateNotes);

View File

@@ -1,3 +0,0 @@
export interface NotesHandlers {
'notes-save': (arg: { id; note }) => Promise<unknown>;
}

View File

@@ -11,8 +11,6 @@ import { requiredFields } from '../models';
import { mutator } from '../mutators';
import { undoable } from '../undo';
import { ReportsHandlers } from './types/handlers';
export const reportModel = {
validate(
report: Omit<CustomReportEntity, 'tombstone'>,
@@ -148,10 +146,16 @@ async function updateReport(item: CustomReportEntity) {
await db.updateWithSchema('custom_reports', reportModel.fromJS(item));
}
async function deleteReport(id: string) {
async function deleteReport(id: CustomReportEntity['id']) {
await db.delete_('custom_reports', id);
}
export type ReportsHandlers = {
'report/create': typeof createReport;
'report/update': typeof updateReport;
'report/delete': typeof deleteReport;
};
// Expose functions to the client
export const app = createApp<ReportsHandlers>();

View File

@@ -1,9 +0,0 @@
import { type CustomReportEntity } from '../../../types/models';
export interface ReportsHandlers {
'report/create': (report: CustomReportEntity) => Promise<string>;
'report/update': (report: CustomReportEntity) => Promise<void>;
'report/delete': (id: string) => Promise<void>;
}

View File

@@ -1,5 +1,9 @@
// @ts-strict-ignore
import { type RuleEntity } from '../../types/models';
import {
RuleActionEntity,
TransactionEntity,
type RuleEntity,
} from '../../types/models';
import { createApp } from '../app';
import { RuleError } from '../errors';
import { mutator } from '../mutators';
@@ -7,8 +11,6 @@ import { batchMessages } from '../sync';
import * as rules from '../transactions/transaction-rules';
import { undoable } from '../undo';
import { RulesHandlers } from './types/handlers';
import { Condition, Action, rankRules } from '.';
function validateRule(rule: Partial<RuleEntity>) {
@@ -58,90 +60,127 @@ function validateRule(rule: Partial<RuleEntity>) {
return null;
}
type ValidationError = {
conditionErrors: string[];
actionErrors: string[];
};
export type RulesHandlers = {
'rule-validate': typeof ruleValidate;
'rule-add': typeof addRule;
'rule-update': typeof updateRule;
'rule-delete': typeof deleteRule;
'rule-delete-all': typeof deleteAllRules;
'rule-apply-actions': typeof applyRuleActions;
'rule-add-payee-rename': typeof addRulePayeeRename;
'rules-get': typeof getRules;
'rule-get': typeof getRule;
'rules-run': typeof runRules;
};
// Expose functions to the client
export const app = createApp<RulesHandlers>();
app.method('rule-validate', async function (rule) {
app.method('rule-validate', ruleValidate);
app.method('rule-add', mutator(addRule));
app.method('rule-update', mutator(updateRule));
app.method('rule-delete', mutator(deleteRule));
app.method('rule-delete-all', mutator(deleteAllRules));
app.method('rule-apply-actions', mutator(undoable(applyRuleActions)));
app.method('rule-add-payee-rename', mutator(addRulePayeeRename));
app.method('rules-get', getRules);
app.method('rule-get', getRule);
app.method('rules-run', runRules);
async function ruleValidate(
rule: Partial<RuleEntity>,
): Promise<{ error: ValidationError | null }> {
const error = validateRule(rule);
return { error };
});
}
app.method(
'rule-add',
mutator(async function (rule) {
const error = validateRule(rule);
if (error) {
return { error };
}
async function addRule(
rule: Omit<RuleEntity, 'id'>,
): Promise<{ error: ValidationError } | RuleEntity> {
const error = validateRule(rule);
if (error) {
return { error };
}
const id = await rules.insertRule(rule);
return { id, ...rule };
}),
);
const id = await rules.insertRule(rule);
return { id, ...rule };
}
app.method(
'rule-update',
mutator(async function (rule) {
const error = validateRule(rule);
if (error) {
return { error };
}
async function updateRule<
PartialRule extends Partial<Omit<RuleEntity, 'id'>> & Pick<RuleEntity, 'id'>,
>(rule: PartialRule): Promise<{ error: ValidationError } | PartialRule> {
const error = validateRule(rule);
if (error) {
return { error };
}
await rules.updateRule(rule);
return rule;
}),
);
await rules.updateRule(rule);
return rule;
}
app.method(
'rule-delete',
mutator(async function (id) {
return rules.deleteRule(id);
}),
);
async function deleteRule(id: RuleEntity['id']) {
return rules.deleteRule(id);
}
app.method(
'rule-delete-all',
mutator(async function (ids) {
let someDeletionsFailed = false;
async function deleteAllRules(
ids: Array<RuleEntity['id']>,
): Promise<{ someDeletionsFailed: boolean }> {
let someDeletionsFailed = false;
await batchMessages(async () => {
for (const id of ids) {
const res = await rules.deleteRule(id);
if (res === false) {
someDeletionsFailed = true;
}
await batchMessages(async () => {
for (const id of ids) {
const res = await rules.deleteRule(id);
if (res === false) {
someDeletionsFailed = true;
}
});
}
});
return { someDeletionsFailed };
}),
);
return { someDeletionsFailed };
}
app.method(
'rule-apply-actions',
mutator(
undoable(async function ({ transactions, actions }) {
return rules.applyActions(transactions, actions);
}),
),
);
async function applyRuleActions({
transactions,
actions,
}: {
transactions: TransactionEntity[];
actions: Array<Action | RuleActionEntity>;
}): Promise<null | { added: TransactionEntity[]; updated: unknown[] }> {
return rules.applyActions(transactions, actions);
}
app.method(
'rule-add-payee-rename',
mutator(async function ({ fromNames, to }) {
return rules.updatePayeeRenameRule(fromNames, to);
}),
);
async function addRulePayeeRename({
fromNames,
to,
}: {
fromNames: string[];
to: string;
}): Promise<string> {
return rules.updatePayeeRenameRule(fromNames, to);
}
app.method('rules-get', async function () {
return rankRules(rules.getRules()).map(rule => rule.serialize());
});
app.method('rule-get', async function ({ id }) {
async function getRule({
id,
}: {
id: RuleEntity['id'];
}): Promise<RuleEntity | null> {
const rule = rules.getRules().find(rule => rule.id === id);
return rule ? rule.serialize() : null;
});
}
app.method('rules-run', async function ({ transaction }) {
async function getRules() {
return rankRules(rules.getRules()).map(rule => rule.serialize());
}
async function runRules({
transaction,
}: {
transaction: TransactionEntity;
}): Promise<TransactionEntity> {
return rules.runRules(transaction);
});
}

View File

@@ -1,53 +0,0 @@
// @ts-strict-ignore
import { type Action } from '..';
import {
type RuleEntity,
type TransactionEntity,
type RuleActionEntity,
} from '../../../types/models';
type ValidationError = {
conditionErrors: string[];
actionErrors: string[];
};
export interface RulesHandlers {
'rule-validate': (
rule: Partial<RuleEntity>,
) => Promise<{ error: ValidationError | null }>;
'rule-add': (
rule: Omit<RuleEntity, 'id'>,
) => Promise<{ error: ValidationError } | RuleEntity>;
'rule-update': <
PartialRule extends Partial<Omit<RuleEntity, 'id'>> &
Pick<RuleEntity, 'id'>,
>(
rule: PartialRule,
) => Promise<{ error: ValidationError } | PartialRule>;
'rule-delete': (id: string) => Promise<boolean>;
'rule-delete-all': (
ids: string[],
) => Promise<{ someDeletionsFailed: boolean }>;
'rule-apply-actions': (arg: {
transactions: TransactionEntity[];
actions: Array<Action | RuleActionEntity>;
}) => Promise<null | { added: TransactionEntity[]; updated: unknown[] }>;
'rule-add-payee-rename': (arg: {
fromNames: string[];
to: string;
}) => Promise<string>;
'rules-get': () => Promise<RuleEntity[]>;
'rule-get': (arg: { id: RuleEntity['id'] }) => Promise<RuleEntity>;
'rules-run': (arg: {
transaction: TransactionEntity;
}) => Promise<TransactionEntity>;
}

View File

@@ -16,6 +16,7 @@ import {
getStatus,
recurConfigToRSchedule,
} from '../../shared/schedules';
import { ScheduleEntity } from '../../types/models';
import { addTransactions } from '../accounts/sync';
import { createApp } from '../app';
import { runQuery as aqlQuery } from '../aql';
@@ -35,8 +36,6 @@ import { undoable } from '../undo';
import { Schedule as RSchedule } from '../util/rschedule';
import { findSchedules } from './find-schedules';
import { SchedulesHandlers } from './types/handlers';
// Utilities
function zip(arr1, arr2) {
@@ -185,7 +184,7 @@ async function checkIfScheduleExists(name, scheduleId) {
export async function createSchedule({
schedule = null,
conditions = [],
} = {}) {
} = {}): Promise<ScheduleEntity['id']> {
const scheduleId = schedule?.id || uuidv4();
const { date: dateCond } = extractScheduleConds(conditions);
@@ -516,6 +515,17 @@ async function advanceSchedulesService(syncSuccess) {
}
}
export type SchedulesHandlers = {
'schedule/create': typeof createSchedule;
'schedule/update': typeof updateSchedule;
'schedule/delete': typeof deleteSchedule;
'schedule/skip-next-date': typeof skipNextDate;
'schedule/post-transaction': typeof postTransactionForSchedule;
'schedule/force-run-service': typeof advanceSchedulesService;
'schedule/discover': typeof discoverSchedules;
'schedule/get-upcoming-dates': typeof getUpcomingDates;
};
// Expose functions to the client
export const app = createApp<SchedulesHandlers>();

View File

@@ -13,8 +13,6 @@ import { fromDateRepr } from '../models';
import { conditionsToAQL } from '../transactions/transaction-rules';
import { Schedule as RSchedule } from '../util/rschedule';
import { SchedulesHandlers } from './types/handlers';
function takeDates(config) {
const schedule = new RSchedule({ rrules: recurConfigToRSchedule(config) });
return schedule
@@ -385,8 +383,7 @@ export async function findSchedules() {
},
);
const finalized: Awaited<ReturnType<SchedulesHandlers['schedule/discover']>> =
[];
const finalized: Awaited<ReturnType<typeof findStartDate>> = [];
for (const schedule of schedules) {
finalized.push(await findStartDate(schedule));
}

View File

@@ -1,38 +0,0 @@
// @ts-strict-ignore
import { DiscoverScheduleEntity, ScheduleEntity } from '../../../types/models';
export interface SchedulesHandlers {
'schedule/create': (arg: {
schedule: {
id?: string;
name?: string;
posts_transaction?: boolean;
};
conditions: unknown[];
}) => Promise<string>;
'schedule/update': (schedule: {
schedule;
conditions?;
resetNextDate?: boolean;
}) => Promise<ScheduleEntity['id']>;
'schedule/delete': (arg: { id: ScheduleEntity['id'] }) => Promise<void>;
'schedule/skip-next-date': (arg: {
id: ScheduleEntity['id'];
}) => Promise<void>;
'schedule/post-transaction': (arg: {
id: ScheduleEntity['id'];
}) => Promise<void>;
'schedule/force-run-service': () => Promise<unknown>;
'schedule/discover': () => Promise<DiscoverScheduleEntity[]>;
'schedule/get-upcoming-dates': (arg: {
config;
count: number;
}) => Promise<string[]>;
}

View File

@@ -1,16 +1,28 @@
// @ts-strict-ignore
import { q } from '../../shared/query';
import { TransactionEntity } from '../../types/models';
import { createApp } from '../app';
import { runQuery } from '../aql';
import * as db from '../db';
import { runMutator } from '../mutators';
import { batchUpdateTransactions } from '../transactions';
import { ToolsHandlers } from './types/handlers';
export type ToolsHandlers = {
'tools/fix-split-transactions': typeof fixSplitTransactions;
};
export const app = createApp<ToolsHandlers>();
app.method('tools/fix-split-transactions', async () => {
app.method('tools/fix-split-transactions', fixSplitTransactions);
async function fixSplitTransactions(): Promise<{
numBlankPayees: number;
numCleared: number;
numDeleted: number;
numTransfersFixed: number;
numNonParentErrorsFixed: number;
mismatchedSplits: TransactionEntity[];
}> {
// 1. Check for child transactions that have a blank payee, and set
// the payee to whatever the parent has
const blankPayeeRows = await db.all<
@@ -119,4 +131,4 @@ app.method('tools/fix-split-transactions', async () => {
numNonParentErrorsFixed: errorRows.length,
mismatchedSplits,
};
});
}

View File

@@ -1,12 +0,0 @@
import { TransactionEntity } from '../../../types/models';
export interface ToolsHandlers {
'tools/fix-split-transactions': () => Promise<{
numBlankPayees: number;
numCleared: number;
numDeleted: number;
numTransfersFixed: number;
numNonParentErrorsFixed: number;
mismatchedSplits: TransactionEntity[];
}>;
}

View File

@@ -1,15 +1,15 @@
import type { AccountHandlers } from '../server/accounts/app';
import type { AdminHandlers } from '../server/admin/types/handlers';
import type { AdminHandlers } from '../server/admin/app';
import type { BudgetHandlers } from '../server/budget/app';
import type { DashboardHandlers } from '../server/dashboard/types/handlers';
import type { FiltersHandlers } from '../server/filters/types/handlers';
import type { NotesHandlers } from '../server/notes/types/handlers';
import type { DashboardHandlers } from '../server/dashboard/app';
import type { FiltersHandlers } from '../server/filters/app';
import type { NotesHandlers } from '../server/notes/app';
import type { PayeesHandlers } from '../server/payees/app';
import type { PreferencesHandlers } from '../server/preferences/app';
import type { ReportsHandlers } from '../server/reports/types/handlers';
import type { RulesHandlers } from '../server/rules/types/handlers';
import type { SchedulesHandlers } from '../server/schedules/types/handlers';
import type { ToolsHandlers } from '../server/tools/types/handlers';
import type { ReportsHandlers } from '../server/reports/app';
import type { RulesHandlers } from '../server/rules/app';
import type { SchedulesHandlers } from '../server/schedules/app';
import type { ToolsHandlers } from '../server/tools/app';
import type { TransactionHandlers } from '../server/transactions/app';
import type { ApiHandlers } from './api-handlers';

View File

@@ -16,4 +16,4 @@ export type * from './simplefin';
export type * from './transaction';
export type * from './transaction-filter';
export type * from './user';
export type * from './userAccess';
export type * from './user-access';

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Update server handlers to use the implementation function type instead of duplicating the function arguments/return types