Skip to content

Commit

Permalink
prevent load functions from rerunning excessively (#1115)
Browse files Browse the repository at this point in the history
* prevent load functions from re-running excessively

* clarifying docs

* fix checks
  • Loading branch information
efstajas committed Jun 24, 2024
1 parent a8ce7b4 commit a3e3f43
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 38 deletions.
2 changes: 1 addition & 1 deletion src/lib/flows/create-stream-flow/input-details.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
import { getAddressDriverClient, getCallerClient } from '$lib/utils/get-drips-clients';
import assert from '$lib/utils/assert';
import { waitForAccountMetadata } from '$lib/utils/ipfs';
import { invalidateAll } from '$app/navigation';
import { invalidateAll } from '$lib/stores/fetched-data-cache/invalidate';
const dispatch = createEventDispatcher<StepComponentEvents>();
Expand Down
2 changes: 1 addition & 1 deletion src/lib/flows/edit-stream-flow/enter-new-details.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
import { buildEditStreamBatch } from '$lib/utils/streams/streams';
import assert from '$lib/utils/assert';
import { waitForAccountMetadata } from '$lib/utils/ipfs';
import { invalidateAll } from '$app/navigation';
import { invalidateAll } from '$lib/stores/fetched-data-cache/invalidate';
import walletStore from '$lib/stores/wallet/wallet.store';
const dispatch = createEventDispatcher<StepComponentEvents>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { invalidate } from '$app/navigation';
import { invalidate } from '$lib/stores/fetched-data-cache/invalidate';
import { makeStep } from '$lib/components/stepper/types';
import SuccessStep from '$lib/components/success-step/success-step.svelte';
import mapFilterUndefined from '$lib/utils/map-filter-undefined';
Expand Down
2 changes: 1 addition & 1 deletion src/lib/flows/pause-flow/pause.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
} from './__generated__/gql.generated';
import { buildPauseStreamPopulatedTx } from '$lib/utils/streams/streams';
import query from '$lib/graphql/dripsQL';
import { invalidateAll } from '$app/navigation';
import { invalidateAll } from '$lib/stores/fetched-data-cache/invalidate';
const dispatch = createEventDispatcher<StepComponentEvents>();
Expand Down
2 changes: 1 addition & 1 deletion src/lib/flows/unpause-flow/unpause.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
} from './__generated__/gql.generated';
import { buildUnpauseStreamPopulatedTx } from '$lib/utils/streams/streams';
import query from '$lib/graphql/dripsQL';
import { invalidateAll } from '$app/navigation';
import { invalidateAll } from '$lib/stores/fetched-data-cache/invalidate';
const dispatch = createEventDispatcher<StepComponentEvents>();
Expand Down
2 changes: 1 addition & 1 deletion src/lib/flows/vote/vote.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import ListEditor from '$lib/components/list-editor/list-editor.svelte';
import type { Writable } from 'svelte/store';
import type { State } from './vote-flow-steps';
import { invalidateAll } from '$app/navigation';
import { invalidateAll } from '$lib/stores/fetched-data-cache/invalidate';
import type { VotingRound } from '$lib/utils/multiplayer/schemas';
const dispatch = createEventDispatcher<StepComponentEvents>();
Expand Down
60 changes: 60 additions & 0 deletions src/lib/stores/fetched-data-cache/fetched-data-cache.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { get, writable } from 'svelte/store';

interface State {
data: Record<string, unknown>;
}

const state = writable<State>({ data: {} });

function set(key: string, value: unknown) {
state.update((s) => {
s.data[key] = value;
return s;
});
}

export function clear(key: string) {
state.update((s) => {
delete s.data[key];
return s;
});
}

export function clearAll() {
state.set({ data: {} });
}

/**
* Create a local "fetched data cache" that can be used to prevent data from being re-fetched
* on navigations to a page that has already previously been navigated to.
*
* **Important**: There is no magic for removing data from this store. If data of a page that uses
* this cache should be re-fetched, ensure that `invalidate` functions from `./invalidate.ts` are
* called with the appropriate keys.
*
* **Also important**: No runtime type checking is being done. You need to make sure that
* 1. The key you pass to this function is unique
* 2. The type you pass to this function is correct
*
* **Also important**: What this returns is only a wrapper around a global store. Even if this
* wrapper is abandoned, the values it wrote persist in the global store.
* @param key
* @returns
*/
export const makeFetchedDataCache = <T extends Record<string, unknown> | unknown[]>(
key: string,
) => {
function read(): T | undefined {
return get(state).data[key] as T;
}

function write(v: T) {
set(key, v);
}

return {
subscribe: state.subscribe,
read,
write,
};
};
19 changes: 19 additions & 0 deletions src/lib/stores/fetched-data-cache/invalidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { invalidateAll as skInvalidateAll, invalidate as skInvalidate } from '$app/navigation';
import { clear, clearAll } from './fetched-data-cache.store';

/**
* Invalidate `key` in the local fetched data cache and re-run all load functions
* that depend on `key`.
*/
export async function invalidate(key: string) {
clear(key);
await skInvalidate(key);
}

/**
* Invalidate all keys in the local fetched data cache and re-run all currently active load functions.
*/
export async function invalidateAll() {
clearAll();
await skInvalidateAll();
}
2 changes: 1 addition & 1 deletion src/lib/stores/wallet/wallet.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import storedWritable from '@efstajas/svelte-stored-writable';
import { z } from 'zod';
import { isWalletUnlocked } from './utils/is-wallet-unlocked';
import network, { getNetwork, isConfiguredChainId, type Network } from './network';
import { invalidateAll } from '$app/navigation';
import { invalidateAll } from '../fetched-data-cache/invalidate';

const appsSdk = new SafeAppsSDK();

Expand Down
7 changes: 5 additions & 2 deletions src/routes/app/(app)/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { PROJECT_CARD_FRAGMENT } from '$lib/components/project-card/project-card.svelte';
import query from '$lib/graphql/dripsQL';
import { gql } from 'graphql-request';
import type { ProjectsQuery, ProjectsQueryVariables } from './__generated__/gql.generated';
import type {
ExploreProjectsQuery,
ExploreProjectsQueryVariables,
} from './__generated__/gql.generated';
import {
ProjectSortField,
ProjectVerificationStatus,
Expand Down Expand Up @@ -73,7 +76,7 @@ export const load = async ({ fetch }) => {
);

const fetchProjects = async () => {
const projectsRes = await query<ProjectsQuery, ProjectsQueryVariables>(
const projectsRes = await query<ExploreProjectsQuery, ExploreProjectsQueryVariables>(
getProjectsQuery,
getProjectsVariables,
fetch,
Expand Down
31 changes: 25 additions & 6 deletions src/routes/app/(app)/drip-lists/+page.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import query from '$lib/graphql/dripsQL.js';
import { error, redirect } from '@sveltejs/kit';
import { redirect } from '@sveltejs/kit';
import { gql } from 'graphql-request';
import { DRIP_LISTS_PAGE_DRIP_LIST_FRAGMENT } from './+page.svelte';
import { getVotingRounds } from '$lib/utils/multiplayer';
import { mapSplitsFromMultiplayerResults } from '$lib/components/splits/splits.svelte';
import {
mapSplitsFromMultiplayerResults,
type SplitsComponentSplitsReceiver,
} from '$lib/components/splits/splits.svelte';
import type {
DripListsPageQuery,
DripListsPageQueryVariables,
} from './__generated__/gql.generated';
import buildUrl from '$lib/utils/build-url';
import getConnectedAddress from '$lib/utils/get-connected-address';
import { makeFetchedDataCache } from '$lib/stores/fetched-data-cache/fetched-data-cache.store';
import type { VotingRound } from '$lib/utils/multiplayer/schemas';

type VotingRoundWithSplits = VotingRound & { splits: SplitsComponentSplitsReceiver[] };

const fetchedDataCache = makeFetchedDataCache<{
dripLists: DripListsPageQuery['dripLists'];
votingRounds: VotingRoundWithSplits[];
}>('dashboard:drip-lists');

export const load = async ({ fetch }) => {
const connectedAddress = getConnectedAddress();

if (!connectedAddress) {
redirect(307, buildUrl('/app/connect', { backTo: '/app/drip-lists' }));
throw redirect(307, buildUrl('/app/connect', { backTo: '/app/drip-lists' }));
}

const dripListsPageQuery = gql`
Expand All @@ -27,6 +39,12 @@ export const load = async ({ fetch }) => {
}
`;

const locallyCached = fetchedDataCache.read();

if (locallyCached) {
return locallyCached;
}

const [votingRounds, dripListsRes] = await Promise.all([
await getVotingRounds({ publisherAddress: connectedAddress }, fetch),
await query<DripListsPageQuery, DripListsPageQueryVariables>(
Expand All @@ -47,9 +65,10 @@ export const load = async ({ fetch }) => {
splits: votingRoundsSplits[votingRoundsWithResults.findIndex((vR) => vR.id === v.id)] ?? [],
}));

if (!connectedAddress) {
return error(401, 'Unauthorized');
}
fetchedDataCache.write({
dripLists: dripListsRes.dripLists,
votingRounds: votingRoundsWithSplits,
});

return { dripLists: dripListsRes.dripLists, votingRounds: votingRoundsWithSplits };
};
Expand Down
21 changes: 14 additions & 7 deletions src/routes/app/(app)/funds/+page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { redirect } from '@sveltejs/kit';
import { USER_BALANCES_FRAGMENT } from './sections/balances.section.svelte';
import buildUrl from '$lib/utils/build-url';
import getConnectedAddress from '$lib/utils/get-connected-address';
import { makeFetchedDataCache } from '$lib/stores/fetched-data-cache/fetched-data-cache.store';

const fetchedDataCache = makeFetchedDataCache<UserStreamsQuery>('dashboard:funds');

export const load = async ({ fetch }) => {
const connectedAddress = getConnectedAddress();
Expand All @@ -29,13 +32,17 @@ export const load = async ({ fetch }) => {
}
`;

const res = await query<UserStreamsQuery, UserStreamsQueryVariables>(
streamsQuery,
{
connectedAddress,
},
fetch,
);
const res =
fetchedDataCache.read() ??
(await query<UserStreamsQuery, UserStreamsQueryVariables>(
streamsQuery,
{
connectedAddress,
},
fetch,
));

fetchedDataCache.write(res);

return {
streams: res.userByAddress.streams,
Expand Down
17 changes: 12 additions & 5 deletions src/routes/app/(app)/projects/+page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import type { ProjectsPageQuery, ProjectsPageQueryVariables } from './__generate
import { redirect } from '@sveltejs/kit';
import buildUrl from '$lib/utils/build-url';
import getConnectedAddress from '$lib/utils/get-connected-address';
import { makeFetchedDataCache } from '$lib/stores/fetched-data-cache/fetched-data-cache.store';

const fetchedDataCache = makeFetchedDataCache<ProjectsPageQuery>('dashboard:projects');

export const load = async ({ fetch }) => {
const connectedAddress = getConnectedAddress();
Expand All @@ -22,11 +25,15 @@ export const load = async ({ fetch }) => {
}
`;

const res = await query<ProjectsPageQuery, ProjectsPageQueryVariables>(
projectsQuery,
{ address: connectedAddress },
fetch,
);
const res =
fetchedDataCache.read() ??
(await query<ProjectsPageQuery, ProjectsPageQueryVariables>(
projectsQuery,
{ address: connectedAddress },
fetch,
));

fetchedDataCache.write(res);

return { projects: res.projects };
};
Expand Down
29 changes: 18 additions & 11 deletions src/routes/app/+layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,32 @@ import query from '$lib/graphql/dripsQL.js';
import { gql } from 'graphql-request';
import type { UserQuery, UserQueryVariables } from './__generated__/gql.generated';
import getConnectedAddress from '$lib/utils/get-connected-address.js';
import { makeFetchedDataCache } from '$lib/stores/fetched-data-cache/fetched-data-cache.store';

const fetchedDataCache = makeFetchedDataCache<UserQuery>('app-layout:user');

export const load = async ({ url: { pathname }, fetch, depends }) => {
const connectedAddress = getConnectedAddress();

if (connectedAddress) {
depends('app-layout:user');

const user = await query<UserQuery, UserQueryVariables>(
gql`
${HEADER_USER_FRAGMENT}
query User($connectedAddress: String!) {
userByAddress(address: $connectedAddress) {
...HeaderUser
const user =
fetchedDataCache.read() ??
(await query<UserQuery, UserQueryVariables>(
gql`
${HEADER_USER_FRAGMENT}
query User($connectedAddress: String!) {
userByAddress(address: $connectedAddress) {
...HeaderUser
}
}
}
`,
{ connectedAddress },
fetch,
);
`,
{ connectedAddress },
fetch,
));

fetchedDataCache.write(user);

return { user: user.userByAddress, pathname };
}
Expand Down

0 comments on commit a3e3f43

Please sign in to comment.