Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TypeScript] Make db.first generic to make it easy to type DB query results #4248

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/loot-core/src/mocks/budget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,14 +461,14 @@ async function fillOther(handlers, account, payees, groups) {
async function createBudget(accounts, payees, groups) {
const primaryAccount = accounts.find(a => (a.name = 'Bank of America'));
const earliestDate = (
await db.first(
`SELECT * FROM v_transactions t LEFT JOIN accounts a ON t.account = a.id
await db.first<Pick<db.DbViewTransaction, 'date'>>(
`SELECT t.date FROM v_transactions t LEFT JOIN accounts a ON t.account = a.id
WHERE a.offbudget = 0 AND t.is_child = 0 ORDER BY date ASC LIMIT 1`,
)
).date;
const earliestPrimaryDate = (
await db.first(
`SELECT * FROM v_transactions t LEFT JOIN accounts a ON t.account = a.id
await db.first<Pick<db.DbViewTransaction, 'date'>>(
`SELECT t.date FROM v_transactions t LEFT JOIN accounts a ON t.account = a.id
WHERE a.id = ? AND a.offbudget = 0 AND t.is_child = 0 ORDER BY date ASC LIMIT 1`,
[primaryAccount.id],
)
Expand Down
2 changes: 1 addition & 1 deletion packages/loot-core/src/server/accounts/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid';
import * as db from '../db';

export async function findOrCreateBank(institution, requisitionId) {
const bank = await db.first(
const bank = await db.first<Pick<db.DbBank, 'id' | 'bank_id'>>(
'SELECT id, bank_id, name FROM banks WHERE bank_id = ?',
[requisitionId],
);
Expand Down
6 changes: 3 additions & 3 deletions packages/loot-core/src/server/accounts/payees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as db from '../db';
export async function createPayee(description) {
// Check to make sure no payee already exists with exactly the same
// name
const row = await db.first(
const row = await db.first<Pick<db.DbPayee, 'id'>>(
`SELECT id FROM payees WHERE UNICODE_LOWER(name) = ? AND tombstone = 0`,
[description.toLowerCase()],
);
Expand All @@ -17,14 +17,14 @@ export async function createPayee(description) {
}

export async function getStartingBalancePayee() {
let category = await db.first(`
let category = await db.first<db.DbCategory>(`
SELECT * FROM categories
WHERE is_income = 1 AND
LOWER(name) = 'starting balances' AND
tombstone = 0
`);
if (category === null) {
category = await db.first(
category = await db.first<db.DbCategory>(
'SELECT * FROM categories WHERE is_income = 1 AND tombstone = 0',
);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/loot-core/src/server/accounts/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ export async function matchTransactions(
);

// The first pass runs the rules, and preps data for fuzzy matching
const accounts: AccountEntity[] = await db.getAccounts();
const accounts: db.DbAccount[] = await db.getAccounts();
const accountsMap = new Map(accounts.map(account => [account.id, account]));

const transactionsStep1 = [];
Expand All @@ -593,7 +593,7 @@ export async function matchTransactions(
// is the highest fidelity match and should always be attempted
// first.
if (trans.imported_id) {
match = await db.first(
match = await db.first<db.DbViewTransaction>(
'SELECT * FROM v_transactions WHERE imported_id = ? AND account = ?',
[trans.imported_id, acctId],
);
Expand Down Expand Up @@ -727,7 +727,7 @@ export async function addTransactions(
{ rawPayeeName: true },
);

const accounts: AccountEntity[] = await db.getAccounts();
const accounts: db.DbAccount[] = await db.getAccounts();
const accountsMap = new Map(accounts.map(account => [account.id, account]));

for (const { trans: originalTrans, subtransactions } of normalized) {
Expand Down
7 changes: 4 additions & 3 deletions packages/loot-core/src/server/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@ async function validateExpenseCategory(debug, id) {
throw APIError(`${debug}: category id is required`);
}

const row = await db.first('SELECT is_income FROM categories WHERE id = ?', [
id,
]);
const row = await db.first<Pick<db.DbCategory, 'is_income'>>(
'SELECT is_income FROM categories WHERE id = ?',
[id],
);

if (!row) {
throw APIError(`${debug}: category “${id}” does not exist`);
Expand Down
4 changes: 2 additions & 2 deletions packages/loot-core/src/server/budget/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ export async function setNMonthAvg({
N: number;
category: string;
}): Promise<void> {
const categoryFromDb = await db.first(
const categoryFromDb = await db.first<Pick<db.DbViewCategory, 'is_income'>>(
'SELECT is_income FROM v_categories WHERE id = ?',
[category],
);
Expand Down Expand Up @@ -361,7 +361,7 @@ export async function holdForNextMonth({
month: string;
amount: number;
}): Promise<boolean> {
const row = await db.first(
const row = await db.first<Pick<db.DbZeroBudgetMonth, 'buffered'>>(
'SELECT buffered FROM zero_budget_months WHERE id = ?',
[month],
);
Expand Down
2 changes: 1 addition & 1 deletion packages/loot-core/src/server/budget/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ export async function createBudget(months) {
}

export async function createAllBudgets() {
const earliestTransaction = await db.first(
const earliestTransaction = await db.first<db.DbTransaction>(
'SELECT * FROM transactions WHERE isChild=0 AND date IS NOT NULL ORDER BY date ASC LIMIT 1',
);
const earliestDate =
Expand Down
6 changes: 3 additions & 3 deletions packages/loot-core/src/server/budget/cleanup-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async function applyGroupCleanups(
);
const to_budget = budgeted + Math.abs(balance);
const categoryId = generalGroup[ii].category;
let carryover = await db.first(
let carryover = await db.first<Pick<db.DbZeroBudget, 'carryover'>>(
`SELECT carryover FROM zero_budgets WHERE month = ? and category = ?`,
[db_month, categoryId],
);
Expand Down Expand Up @@ -220,7 +220,7 @@ async function processCleanup(month: string): Promise<Notification> {
} else {
warnings.push(category.name + ' does not have available funds.');
}
const carryover = await db.first(
const carryover = await db.first<Pick<db.DbZeroBudget, 'carryover'>>(
`SELECT carryover FROM zero_budgets WHERE month = ? and category = ?`,
[db_month, category.id],
);
Expand Down Expand Up @@ -249,7 +249,7 @@ async function processCleanup(month: string): Promise<Notification> {
const budgeted = await getSheetValue(sheetName, `budget-${category.id}`);
const to_budget = budgeted + Math.abs(balance);
const categoryId = category.id;
let carryover = await db.first(
let carryover = await db.first<Pick<db.DbZeroBudget, 'carryover'>>(
`SELECT carryover FROM zero_budgets WHERE month = ? and category = ?`,
[db_month, categoryId],
);
Expand Down
6 changes: 4 additions & 2 deletions packages/loot-core/src/server/budget/goalsSchedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ async function createScheduleList(
const errors = [];

for (let ll = 0; ll < template.length; ll++) {
const { id: sid, completed: complete } = await db.first(
'SELECT * FROM schedules WHERE TRIM(name) = ? AND tombstone = 0',
const { id: sid, completed: complete } = await db.first<
Pick<db.DbSchedule, 'id' | 'completed'>
>(
'SELECT id, completed FROM schedules WHERE TRIM(name) = ? AND tombstone = 0',
[template[ll].name.trim()],
);
const rule = await getRuleForSchedule(sid);
Expand Down
4 changes: 3 additions & 1 deletion packages/loot-core/src/server/dashboard/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ async function addDashboardWidget(
// If no x & y was provided - calculate it dynamically
// The new widget should be the very last one in the list of all widgets
if (!('x' in widget) && !('y' in widget)) {
const data = await db.first(
const data = await db.first<
Pick<db.DbDashboard, 'x' | 'y' | 'width' | 'height'>
>(
'SELECT x, y, width, height FROM dashboard WHERE tombstone = 0 ORDER BY y DESC, x DESC',
);

Expand Down
40 changes: 22 additions & 18 deletions packages/loot-core/src/server/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
DbAccount,
DbCategory,
DbCategoryGroup,
DbClockMessage,
DbPayee,
DbTransaction,
DbViewTransaction,
Expand Down Expand Up @@ -83,7 +84,7 @@ export function getDatabase() {
}

export async function loadClock() {
const row = await first('SELECT * FROM messages_clock');
const row = await first<DbClockMessage>('SELECT * FROM messages_clock');
if (row) {
const clock = deserializeClock(row.clock);
setClock(clock);
Expand Down Expand Up @@ -166,12 +167,9 @@ export async function all(sql, params?: (string | number)[]) {
return runQuery(sql, params, true) as any[];
}

export async function first(sql, params?: (string | number)[]) {
const arr = await runQuery(sql, params, true);
// TODO: In the next phase, we will make this function generic
// and pass the type of the return type to `runQuery`.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return arr.length === 0 ? null : (arr[0] as any);
export async function first<T>(sql, params?: (string | number)[]) {
const arr = await runQuery<T>(sql, params, true);
return arr.length === 0 ? null : arr[0];
}

// The underlying sql system is now sync, but we can't update `first` yet
Expand Down Expand Up @@ -353,7 +351,9 @@ export async function getCategoriesGrouped(

export async function insertCategoryGroup(group) {
// Don't allow duplicate group
const existingGroup = await first(
const existingGroup = await first<
Pick<DbCategoryGroup, 'id' | 'name' | 'hidden'>
>(
`SELECT id, name, hidden FROM category_groups WHERE UPPER(name) = ? and tombstone = 0 LIMIT 1`,
[group.name.toUpperCase()],
);
Expand All @@ -363,7 +363,7 @@ export async function insertCategoryGroup(group) {
);
}

const lastGroup = await first(`
const lastGroup = await first<Pick<DbCategoryGroup, 'sort_order'>>(`
SELECT sort_order FROM category_groups WHERE tombstone = 0 ORDER BY sort_order DESC, id DESC LIMIT 1
`);
const sort_order = (lastGroup ? lastGroup.sort_order : 0) + SORT_INCREMENT;
Expand Down Expand Up @@ -411,7 +411,7 @@ export async function insertCategory(
let id_;
await batchMessages(async () => {
// Dont allow duplicated names in groups
const existingCatInGroup = await first(
const existingCatInGroup = await first<Pick<DbCategory, 'id'>>(
`SELECT id FROM categories WHERE cat_group = ? and UPPER(name) = ? and tombstone = 0 LIMIT 1`,
[category.cat_group, category.name.toUpperCase()],
);
Expand All @@ -422,7 +422,7 @@ export async function insertCategory(
}

if (atEnd) {
const lastCat = await first(`
const lastCat = await first<Pick<DbCategoryGroup, 'sort_order'>>(`
SELECT sort_order FROM categories WHERE tombstone = 0 ORDER BY sort_order DESC, id DESC LIMIT 1
`);
sort_order = (lastCat ? lastCat.sort_order : 0) + SORT_INCREMENT;
Expand Down Expand Up @@ -507,11 +507,11 @@ export async function deleteCategory(
}

export async function getPayee(id: DbPayee['id']) {
return first(`SELECT * FROM payees WHERE id = ?`, [id]);
return first<DbPayee>(`SELECT * FROM payees WHERE id = ?`, [id]);
}

export async function getAccount(id: DbAccount['id']) {
return first(`SELECT * FROM accounts WHERE id = ?`, [id]);
return first<DbAccount>(`SELECT * FROM accounts WHERE id = ?`, [id]);
}

export async function insertPayee(payee) {
Expand All @@ -525,9 +525,10 @@ export async function insertPayee(payee) {
}

export async function deletePayee(payee: Pick<DbPayee, 'id'>) {
const { transfer_acct } = await first('SELECT * FROM payees WHERE id = ?', [
payee.id,
]);
const { transfer_acct } = await first<DbPayee>(
'SELECT * FROM payees WHERE id = ?',
[payee.id],
);
if (transfer_acct) {
// You should never be able to delete transfer payees
return;
Expand Down Expand Up @@ -654,7 +655,7 @@ export async function getOrphanedPayees() {
}

export async function getPayeeByName(name: DbPayee['name']) {
return first(
return first<DbPayee>(
`SELECT * FROM payees WHERE UNICODE_LOWER(name) = ? AND tombstone = 0`,
[name.toLowerCase()],
);
Expand Down Expand Up @@ -695,7 +696,10 @@ export async function moveAccount(
id: DbAccount['id'],
targetId: DbAccount['id'],
) {
const account = await first('SELECT * FROM accounts WHERE id = ?', [id]);
const account = await first<DbAccount>(
'SELECT * FROM accounts WHERE id = ?',
[id],
);
let accounts;
if (account.closed) {
accounts = await all(
Expand Down
2 changes: 1 addition & 1 deletion packages/loot-core/src/server/filters/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const filterModel = {
};

async function filterNameExists(name, filterId, newItem) {
const idForName = await db.first(
const idForName = await db.first<Pick<db.DbTransactionFilter, 'id'>>(
'SELECT id from transaction_filters WHERE tombstone = 0 AND name = ?',
[name],
);
Expand Down
4 changes: 3 additions & 1 deletion packages/loot-core/src/server/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ describe('Budgets', () => {

// Grab the clock to compare later
await db.openDatabase('test-budget');
const row = await db.first('SELECT * FROM messages_clock');
const row = await db.first<db.DbClockMessage>(
'SELECT * FROM messages_clock',
);

const { error } = await runHandler(handlers['load-budget'], {
id: 'test-budget',
Expand Down
Loading
Loading