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] Server event types #4110

Merged
merged 2 commits into from
Jan 9, 2025
Merged
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
12 changes: 6 additions & 6 deletions packages/desktop-client/src/components/Titlebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) {
>(null);

useEffect(() => {
const unlisten = listen('sync-event', ({ type, subtype, syncDisabled }) => {
if (type === 'start') {
const unlisten = listen('sync-event', event => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to event so typescript can infer discrimanted type better vs destructured.

if (event.type === 'start') {
setSyncing(true);
setSyncState(null);
} else {
Expand All @@ -130,19 +130,19 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) {
}, 200);
}

if (type === 'error') {
if (event.type === 'error') {
// Use the offline state if either there is a network error or
// if this file isn't a "cloud file". You can't sync a local
// file.
if (subtype === 'network') {
if (event.subtype === 'network') {
setSyncState('offline');
} else if (!cloudFileId) {
setSyncState('local');
} else {
setSyncState('error');
}
} else if (type === 'success') {
setSyncState(syncDisabled ? 'disabled' : null);
} else if (event.type === 'success') {
setSyncState(event.syncDisabled ? 'disabled' : null);
}
});

Expand Down
16 changes: 9 additions & 7 deletions packages/desktop-client/src/components/budget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,16 @@ function BudgetInner(props: BudgetInnerProps) {
run();

const unlistens = [
listen('sync-event', ({ type, tables }) => {
if (
type === 'success' &&
(tables.includes('categories') ||
listen('sync-event', event => {
if (event.type === 'success') {
const tables = event.tables;
if (
tables.includes('categories') ||
tables.includes('category_mapping') ||
tables.includes('category_groups'))
) {
loadCategories();
tables.includes('category_groups')
) {
loadCategories();
}
}
}),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,9 @@ function TransactionListWithPreviews({
}, [accountId, dispatch]);

useEffect(() => {
return listen('sync-event', ({ type, tables }) => {
if (type === 'applied') {
return listen('sync-event', event => {
if (event.type === 'applied') {
const tables = event.tables;
if (
tables.includes('transactions') ||
tables.includes('category_mapping') ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ export function CategoryTransactions({
const dateFormat = useDateFormat() || 'MM/dd/yyyy';

useEffect(() => {
return listen('sync-event', ({ type, tables }) => {
if (type === 'applied') {
return listen('sync-event', event => {
if (event.type === 'applied') {
const tables = event.tables;
if (
tables.includes('transactions') ||
tables.includes('category_mapping') ||
Expand Down
18 changes: 10 additions & 8 deletions packages/desktop-client/src/components/mobile/budget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,17 @@ export function Budget() {

init();

const unlisten = listen('sync-event', ({ type, tables }) => {
if (
type === 'success' &&
(tables.includes('categories') ||
const unlisten = listen('sync-event', event => {
if (event.type === 'success') {
const tables = event.tables;
if (
tables.includes('categories') ||
tables.includes('category_mapping') ||
tables.includes('category_groups'))
) {
// TODO: is this loading every time?
dispatch(getCategories());
tables.includes('category_groups')
) {
// TODO: is this loading every time?
dispatch(getCategories());
}
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ export function ManagePayeesWithData({
}
loadData();

const unlisten = listen('sync-event', async ({ type, tables }) => {
if (type === 'applied') {
if (tables.includes('rules')) {
const unlisten = listen('sync-event', async event => {
if (event.type === 'applied') {
if (event.tables.includes('rules')) {
await refetchRuleCounts();
}
}
Expand Down
9 changes: 5 additions & 4 deletions packages/desktop-client/src/global-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,20 @@ export function handleGlobalEvents(actions: BoundActions, store: Store<State>) {
});
});

listen('schedules-offline', ({ payees }) => {
actions.pushModal('schedule-posts-offline-notification', { payees });
listen('schedules-offline', () => {
actions.pushModal('schedule-posts-offline-notification');
});

// This is experimental: we sync data locally automatically when
// data changes from the backend
listen('sync-event', async ({ type, tables }) => {
listen('sync-event', async event => {
// We don't need to query anything until the file is loaded, and
// sync events might come in if the file is being synced before
// being loaded (happens when downloading)
const prefs = store.getState().prefs.local;
if (prefs && prefs.id) {
if (type === 'applied') {
if (event.type === 'applied') {
const tables = event.tables;
if (tables.includes('payees') || tables.includes('payee_mapping')) {
actions.getPayees();
}
Expand Down
15 changes: 9 additions & 6 deletions packages/loot-core/src/client/query-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class LiveQuery<TResponse = unknown> {
private _data: Data<TResponse>;
private _dependencies: Set<string>;
private _listeners: Array<Listener<TResponse>>;
private _supportedSyncTypes: Set<string>;
private _supportedSyncTypes: Set<'applied' | 'success'>;
private _query: Query;
private _onError: (error: Error) => void;

Expand Down Expand Up @@ -107,8 +107,8 @@ export class LiveQuery<TResponse = unknown> {

// TODO: error types?
this._supportedSyncTypes = options.onlySync
? new Set<string>(['success'])
: new Set<string>(['applied', 'success']);
? new Set(['success'])
: new Set(['applied', 'success']);

if (onData) {
this.addListener(onData);
Expand Down Expand Up @@ -162,15 +162,18 @@ export class LiveQuery<TResponse = unknown> {

protected subscribe = () => {
if (this._unsubscribeSyncEvent == null) {
this._unsubscribeSyncEvent = listen('sync-event', ({ type, tables }) => {
this._unsubscribeSyncEvent = listen('sync-event', event => {
// If the user is doing optimistic updates, they don't want to
// always refetch whenever something changes because it would
// refetch all data after they've already updated the UI. This
// voids the perf benefits of optimistic updates. Allow querys
// to only react to remote syncs. By default, queries will
// always update to all changes.
if (this._supportedSyncTypes.has(type)) {
this.onUpdate(tables);
if (
(event.type === 'applied' || event.type === 'success') &&
this._supportedSyncTypes.has(event.type)
) {
this.onUpdate(event.tables);
}
});
}
Expand Down
20 changes: 10 additions & 10 deletions packages/loot-core/src/client/shared-listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ import type { Notification } from './state-types/notifications';
export function listenForSyncEvent(actions, store) {
let attemptedSyncRepair = false;

listen('sync-event', info => {
const { type, subtype, meta, tables } = info;

listen('sync-event', event => {
const prefs = store.getState().prefs.local;
if (!prefs || !prefs.id) {
// Do nothing if no budget is loaded
return;
}

if (type === 'success') {
if (event.type === 'success') {
if (attemptedSyncRepair) {
attemptedSyncRepair = false;

Expand All @@ -28,6 +26,8 @@ export function listenForSyncEvent(actions, store) {
});
}

const tables = event.tables;

if (tables.includes('prefs')) {
actions.loadPrefs();
}
Expand All @@ -47,13 +47,13 @@ export function listenForSyncEvent(actions, store) {
if (tables.includes('accounts')) {
actions.getAccounts();
}
} else if (type === 'error') {
} else if (event.type === 'error') {
let notif: Notification | null = null;
const learnMore = `[${t('Learn more')}](https://actualbudget.org/docs/getting-started/sync/#debugging-sync-issues)`;
const githubIssueLink =
'https://github.com/actualbudget/actual/issues/new?assignees=&labels=bug&template=bug-report.yml&title=%5BBug%5D%3A+';

switch (subtype) {
switch (event.subtype) {
case 'out-of-sync':
if (attemptedSyncRepair) {
notif = {
Expand Down Expand Up @@ -215,7 +215,7 @@ export function listenForSyncEvent(actions, store) {
break;
case 'encrypt-failure':
case 'decrypt-failure':
if (meta.isMissingKey) {
if (event.meta.isMissingKey) {
notif = {
title: t('Missing encryption key'),
message: t(
Expand Down Expand Up @@ -252,7 +252,7 @@ export function listenForSyncEvent(actions, store) {
}
break;
case 'invalid-schema':
console.trace('invalid-schema', meta);
console.trace('invalid-schema', event.meta);
notif = {
title: t('Update required'),
message: t(
Expand All @@ -263,7 +263,7 @@ export function listenForSyncEvent(actions, store) {
};
break;
case 'apply-failure':
console.trace('apply-failure', meta);
console.trace('apply-failure', event.meta);
notif = {
message: t(
'We couldn’t apply that change to the database. Please report this as a bug by [opening a Github issue]({{githubIssueLink}}).',
Expand All @@ -287,7 +287,7 @@ export function listenForSyncEvent(actions, store) {
};
break;
default:
console.trace('unknown error', info);
console.trace('unknown error', event);
notif = {
message: t(
'We had problems syncing your changes. Please report this as a bug by [opening a Github issue]({{githubIssueLink}}).',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type Init = typeof init;

export function send<K extends keyof ServerEvents>(
type: K,
args?: ServerEvents[k],
args?: ServerEvents[K],
): void;
export type Send = typeof send;

Expand Down
4 changes: 2 additions & 2 deletions packages/loot-core/src/server/accounts/transactions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-strict-ignore
import * as connection from '../../platform/server/connection';
import { Diff } from '../../shared/util';
import { TransactionEntity } from '../../types/models';
import { PayeeEntity, TransactionEntity } from '../../types/models';
import * as db from '../db';
import { incrFetch, whereIn } from '../db/util';
import { batchMessages } from '../sync';
Expand Down Expand Up @@ -55,7 +55,7 @@ export async function batchUpdateTransactions({
? await idsWithChildren(deleted.map(d => d.id))
: [];

const oldPayees = new Set();
const oldPayees = new Set<PayeeEntity['id']>();
const accounts = await db.all('SELECT * FROM accounts WHERE tombstone = 0');

// We need to get all the payees of updated transactions _before_
Expand Down
10 changes: 8 additions & 2 deletions packages/loot-core/src/server/app.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
// @ts-strict-ignore
import mitt from 'mitt';
import mitt, { type Emitter } from 'mitt';

import { captureException } from '../platform/exceptions';
import { ServerEvents } from '../types/server-events';

// This is a simple helper abstraction for defining methods exposed to
// the client. It doesn't do much, but checks for naming conflicts and
// makes it cleaner to combine methods. We call a group of related
// methods an "app".

type Events = {
sync: ServerEvents['sync-event'];
'load-budget': { id: string };
};

class App<Handlers> {
events;
events: Emitter<Events>;
handlers: Handlers;
services;
unlistenServices;
Expand Down
4 changes: 2 additions & 2 deletions packages/loot-core/src/server/main-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ import { createApp } from './app';
// Main app
export const app = createApp<Handlers>();

app.events.on('sync', info => {
connection.send('sync-event', info);
app.events.on('sync', event => {
connection.send('sync-event', event);
});
4 changes: 2 additions & 2 deletions packages/loot-core/src/server/schedules/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ async function advanceSchedulesService(syncSuccess) {
}

if (failedToPost.length > 0) {
connection.send('schedules-offline', { payees: failedToPost });
connection.send('schedules-offline');
} else if (didPost) {
// This forces a full refresh of transactions because it
// simulates them coming in from a full sync. This not a
Expand All @@ -542,7 +542,7 @@ async function advanceSchedulesService(syncSuccess) {
connection.send('sync-event', {
type: 'success',
tables: ['transactions'],
syncDisabled: 'false',
syncDisabled: false,
});
}
}
Expand Down
Loading
Loading