Nicolium/pl-api: an attempt at performance improvements
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -77,12 +77,7 @@ const prefetchFollowRequests = (client: PlApiClient) =>
|
||||
queryKey: queryKeys.accountsLists.followRequests,
|
||||
queryFn: ({ pageParam }) =>
|
||||
pageParam.next?.() ?? client.myAccount.getFollowRequests().then(minifyAccountList),
|
||||
initialPageParam: {
|
||||
previous: null,
|
||||
next: null,
|
||||
items: [],
|
||||
partial: false,
|
||||
} as PaginatedResponse<string>,
|
||||
initialPageParam: { next: null as (() => Promise<PaginatedResponse<string>>) | null },
|
||||
});
|
||||
|
||||
export {
|
||||
|
||||
@ -9,7 +9,7 @@ import sumBy from 'lodash/sumBy';
|
||||
import {
|
||||
type Chat,
|
||||
type ChatMessage as BaseChatMessage,
|
||||
type PaginatedResponse,
|
||||
PaginatedResponse,
|
||||
chatMessageSchema,
|
||||
} from 'pl-api';
|
||||
import * as v from 'valibot';
|
||||
@ -44,12 +44,12 @@ const normalizeChatMessagesList = ({
|
||||
next,
|
||||
items,
|
||||
...response
|
||||
}: PaginatedResponse<BaseChatMessage>): PaginatedResponse<ChatMessage> => ({
|
||||
...response,
|
||||
previous: previous ? () => previous().then((res) => normalizeChatMessagesList(res)) : null,
|
||||
next: next ? () => next().then((res) => normalizeChatMessagesList(res)) : null,
|
||||
items: items.map(normalizeChatMessage),
|
||||
});
|
||||
}: PaginatedResponse<BaseChatMessage>): PaginatedResponse<ChatMessage> =>
|
||||
new PaginatedResponse(items.map(normalizeChatMessage), {
|
||||
...response,
|
||||
previous: previous ? () => previous().then((res) => normalizeChatMessagesList(res)) : null,
|
||||
next: next ? () => next().then((res) => normalizeChatMessagesList(res)) : null,
|
||||
});
|
||||
|
||||
const useChatMessages = (chat: Chat) => {
|
||||
const client = useClient();
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { type InfiniteData, useInfiniteQuery, useMutation } from '@tanstack/react-query';
|
||||
import { type InteractionRequest, PaginatedResponse } from 'pl-api';
|
||||
|
||||
import { importEntities } from '@/actions/importer';
|
||||
import { useClient } from '@/hooks/use-client';
|
||||
@ -7,8 +8,6 @@ import { useLoggedIn } from '@/hooks/use-logged-in';
|
||||
|
||||
import { queryKeys } from '../keys';
|
||||
|
||||
import type { InteractionRequest, PaginatedResponse } from 'pl-api';
|
||||
|
||||
const minifyInteractionRequest = ({
|
||||
account,
|
||||
status,
|
||||
@ -31,12 +30,11 @@ const minifyInteractionRequestsList = ({
|
||||
}: PaginatedResponse<InteractionRequest>): PaginatedResponse<MinifiedInteractionRequest> => {
|
||||
importEntities({ statuses: items.flatMap((item) => [item.status, item.reply]) });
|
||||
|
||||
return {
|
||||
return new PaginatedResponse(items.map(minifyInteractionRequest), {
|
||||
...response,
|
||||
previous: previous ? () => previous().then(minifyInteractionRequestsList) : null,
|
||||
next: next ? () => next().then(minifyInteractionRequestsList) : null,
|
||||
items: items.map(minifyInteractionRequest),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const useInteractionRequests = <T>(
|
||||
@ -52,11 +50,8 @@ const useInteractionRequests = <T>(
|
||||
pageParam.next?.() ??
|
||||
client.interactionRequests.getInteractionRequests().then(minifyInteractionRequestsList),
|
||||
initialPageParam: {
|
||||
previous: null,
|
||||
next: null,
|
||||
items: [],
|
||||
partial: false,
|
||||
} as PaginatedResponse<MinifiedInteractionRequest>,
|
||||
next: null as (() => Promise<PaginatedResponse<MinifiedInteractionRequest>>) | null,
|
||||
},
|
||||
getNextPageParam: (page) => (page.next ? page : undefined),
|
||||
enabled: isLoggedIn && features.interactionRequests,
|
||||
select,
|
||||
|
||||
@ -31,12 +31,7 @@ const makePaginatedResponseQueryOptions =
|
||||
infiniteQueryOptions({
|
||||
queryKey: typeof queryKey === 'object' ? queryKey : queryKey(...params),
|
||||
queryFn: ({ pageParam }) => pageParam.next?.() ?? queryFn(getClient(), params),
|
||||
initialPageParam: {
|
||||
previous: null,
|
||||
next: null,
|
||||
items: [] as unknown as PaginatedResponse<T2, IsArray>['items'],
|
||||
partial: false,
|
||||
} as Awaited<ReturnType<typeof queryFn>>,
|
||||
initialPageParam: { next: null as (() => Promise<PaginatedResponse<T2, IsArray>>) | null },
|
||||
getNextPageParam: (page) => (page.next ? page : undefined),
|
||||
select:
|
||||
select ??
|
||||
@ -49,9 +44,7 @@ const makePaginatedResponseQueryOptions =
|
||||
|
||||
if (Array.isArray(lastPage.items)) {
|
||||
const items = PaginatedResponseArray.from(
|
||||
data.pages.flatMap((page) =>
|
||||
Array.isArray(page.items) ? page.items : [page.items],
|
||||
),
|
||||
data.pages.flatMap((page) => (Array.isArray(page.items) ? page.items : [page.items])),
|
||||
).setMeta(lastPage.total, lastPage.partial);
|
||||
|
||||
return items as T3;
|
||||
|
||||
@ -24,8 +24,18 @@ class PaginatedResponseArray<T> extends Array<T> {
|
||||
|
||||
/** Set metadata as non-enumerable to preserve TanStack Query structural sharing. */
|
||||
setMeta(total: number | undefined, partial: boolean | undefined): this {
|
||||
Object.defineProperty(this, 'total', { value: total, writable: true, enumerable: false, configurable: true });
|
||||
Object.defineProperty(this, 'partial', { value: partial, writable: true, enumerable: false, configurable: true });
|
||||
Object.defineProperty(this, 'total', {
|
||||
value: total,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(this, 'partial', {
|
||||
value: partial,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@ -57,12 +67,7 @@ const makePaginatedResponseQuery =
|
||||
return useInfiniteQuery({
|
||||
queryKey: typeof queryKey === 'object' ? queryKey : queryKey(...params),
|
||||
queryFn: ({ pageParam }) => pageParam.next?.() ?? queryFn(client, params),
|
||||
initialPageParam: {
|
||||
previous: null,
|
||||
next: null,
|
||||
items: [] as unknown as PaginatedResponse<T2, IsArray>['items'],
|
||||
partial: false,
|
||||
} as Awaited<ReturnType<typeof queryFn>>,
|
||||
initialPageParam: { next: null as (() => Promise<PaginatedResponse<T2, IsArray>>) | null },
|
||||
getNextPageParam: (page) => (page.next ? page : undefined),
|
||||
select:
|
||||
select ??
|
||||
@ -75,9 +80,7 @@ const makePaginatedResponseQuery =
|
||||
|
||||
if (Array.isArray(lastPage.items)) {
|
||||
const items = PaginatedResponseArray.from(
|
||||
data.pages.flatMap((page) =>
|
||||
Array.isArray(page.items) ? page.items : [page.items],
|
||||
),
|
||||
data.pages.flatMap((page) => (Array.isArray(page.items) ? page.items : [page.items])),
|
||||
).setMeta(lastPage.total, lastPage.partial);
|
||||
|
||||
return items as T3;
|
||||
|
||||
@ -1,24 +1,23 @@
|
||||
import { notifyManager } from '@tanstack/react-query';
|
||||
import {
|
||||
PaginatedResponse,
|
||||
type Account,
|
||||
type AdminAccount,
|
||||
type AdminReport,
|
||||
type BlockedAccount,
|
||||
type Conversation,
|
||||
type Group,
|
||||
type GroupedNotificationsResults,
|
||||
type MutedAccount,
|
||||
type NotificationGroup,
|
||||
type Status,
|
||||
} from 'pl-api';
|
||||
|
||||
import { importEntities } from '@/actions/importer';
|
||||
|
||||
import { queryClient } from '../client';
|
||||
import { queryKeys } from '../keys';
|
||||
|
||||
import type {
|
||||
Account,
|
||||
AdminAccount,
|
||||
AdminReport,
|
||||
BlockedAccount,
|
||||
Conversation,
|
||||
Group,
|
||||
GroupedNotificationsResults,
|
||||
MutedAccount,
|
||||
NotificationGroup,
|
||||
PaginatedResponse,
|
||||
Status,
|
||||
} from 'pl-api';
|
||||
|
||||
const minifyList = <T1, T2, IsArray extends boolean = true>(
|
||||
{ previous, next, items, ...response }: PaginatedResponse<T1, IsArray>,
|
||||
minifier: (value: T1) => T2,
|
||||
@ -31,7 +30,7 @@ const minifyList = <T1, T2, IsArray extends boolean = true>(
|
||||
isArray ? (items as T1[]).map(minifier) : minifier(items as T1)
|
||||
) as PaginatedResponse<T2, IsArray>['items'];
|
||||
|
||||
return {
|
||||
return new PaginatedResponse(minifiedItems, {
|
||||
...response,
|
||||
previous: previous
|
||||
? () =>
|
||||
@ -40,8 +39,7 @@ const minifyList = <T1, T2, IsArray extends boolean = true>(
|
||||
next: next
|
||||
? () => next().then((list) => minifyList<T1, T2, IsArray>(list, minifier, importer, isArray))
|
||||
: null,
|
||||
items: minifiedItems,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const minifyStatusList = (response: PaginatedResponse<Status>): PaginatedResponse<string> =>
|
||||
@ -188,14 +186,14 @@ const minifyAdminReport = ({
|
||||
statuses,
|
||||
...adminReport
|
||||
}: AdminReport) => {
|
||||
minifyAdminAccountList({
|
||||
items: [account, action_taken_by_account, assigned_account, target_account].filter(
|
||||
(a): a is AdminAccount => !!a,
|
||||
minifyAdminAccountList(
|
||||
new PaginatedResponse(
|
||||
[account, action_taken_by_account, assigned_account, target_account].filter(
|
||||
(a): a is AdminAccount => !!a,
|
||||
),
|
||||
{ partial: false },
|
||||
),
|
||||
previous: null,
|
||||
next: null,
|
||||
partial: false,
|
||||
});
|
||||
);
|
||||
|
||||
importEntities({
|
||||
accounts: [
|
||||
|
||||
@ -4,12 +4,12 @@ import { instanceSchema } from './entities/instance';
|
||||
import { filteredArray } from './entities/utils';
|
||||
import { type Features, getFeatures } from './features';
|
||||
import request, { getNextLink, getPrevLink, type RequestBody } from './request';
|
||||
import { PaginatedResponse } from './responses';
|
||||
|
||||
import type { Instance } from './entities/instance';
|
||||
import type { StreamingEvent } from './entities/streaming-event';
|
||||
import type { StreamingParams } from './params/streaming';
|
||||
import type { Response as PlApiResponse } from './request';
|
||||
import type { PaginatedResponse } from './responses';
|
||||
|
||||
interface PlApiClientConstructorOpts {
|
||||
/** Instance object to use by default, to be populated eg. from cache */
|
||||
@ -65,16 +65,18 @@ class PlApiBaseClient {
|
||||
body: RequestBody,
|
||||
schema: v.BaseSchema<any, T, v.BaseIssue<unknown>>,
|
||||
isArray = true as IsArray,
|
||||
): Promise<PaginatedResponse<T, typeof isArray>> => {
|
||||
) => {
|
||||
const targetSchema = isArray ? filteredArray(schema) : schema;
|
||||
|
||||
const processResponse = (response: PlApiResponse<any>) =>
|
||||
({
|
||||
previous: getMore(getPrevLink(response)),
|
||||
next: getMore(getNextLink(response)),
|
||||
items: v.parse(targetSchema, response.json),
|
||||
partial: response.status === 206,
|
||||
}) as PaginatedResponse<T, IsArray>;
|
||||
new PaginatedResponse<T, IsArray>(
|
||||
v.parse(targetSchema, response.json) as IsArray extends true ? Array<T> : T,
|
||||
{
|
||||
previous: getMore(getPrevLink(response)),
|
||||
next: getMore(getNextLink(response)),
|
||||
partial: response.status === 206,
|
||||
},
|
||||
);
|
||||
|
||||
const getMore = (input: string | null) =>
|
||||
input ? () => this.request(input).then(processResponse) : null;
|
||||
|
||||
@ -26,8 +26,8 @@ import {
|
||||
import { filteredArray } from '@/entities/utils';
|
||||
|
||||
import { GOTOSOCIAL, MITRA, PLEROMA } from '../features';
|
||||
import { PaginatedResponse } from '../responses';
|
||||
|
||||
import type { PaginatedResponse } from '../responses';
|
||||
import type { PlApiBaseClient } from '@/client-base';
|
||||
import type {
|
||||
AdminAccount,
|
||||
@ -89,7 +89,7 @@ const paginatedPleromaAccounts = async (
|
||||
|
||||
const adminAccounts = v.parse(filteredArray(adminAccountSchema), response.json?.users);
|
||||
|
||||
return {
|
||||
return new PaginatedResponse(adminAccounts, {
|
||||
previous: params.page
|
||||
? () => paginatedPleromaAccounts(client, { ...params, page: params.page! - 1 })
|
||||
: null,
|
||||
@ -98,10 +98,9 @@ const paginatedPleromaAccounts = async (
|
||||
params.page_size * ((params.page || 1) - 1) + response.json?.users?.length
|
||||
? () => paginatedPleromaAccounts(client, { ...params, page: (params.page || 0) + 1 })
|
||||
: null,
|
||||
items: adminAccounts,
|
||||
partial: response.status === 206,
|
||||
total: response.json?.count,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const paginatedPleromaReports = async (
|
||||
@ -115,7 +114,7 @@ const paginatedPleromaReports = async (
|
||||
): Promise<PaginatedResponse<AdminReport>> => {
|
||||
const response = await client.request('/api/v1/pleroma/admin/reports', { params });
|
||||
|
||||
return {
|
||||
return new PaginatedResponse(v.parse(filteredArray(adminReportSchema), response.json?.reports), {
|
||||
previous: params.page
|
||||
? () => paginatedPleromaReports(client, { ...params, page: params.page! - 1 })
|
||||
: null,
|
||||
@ -124,10 +123,9 @@ const paginatedPleromaReports = async (
|
||||
params.page_size * ((params.page || 1) - 1) + response.json?.reports?.length
|
||||
? () => paginatedPleromaReports(client, { ...params, page: (params.page || 0) + 1 })
|
||||
: null,
|
||||
items: v.parse(filteredArray(adminReportSchema), response.json?.reports),
|
||||
partial: response.status === 206,
|
||||
total: response.json?.total,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const paginatedPleromaStatuses = async (
|
||||
@ -142,16 +140,15 @@ const paginatedPleromaStatuses = async (
|
||||
): Promise<PaginatedResponse<Status>> => {
|
||||
const response = await client.request('/api/v1/pleroma/admin/statuses', { params });
|
||||
|
||||
return {
|
||||
return new PaginatedResponse(v.parse(filteredArray(statusSchema), response.json), {
|
||||
previous: params.page
|
||||
? () => paginatedPleromaStatuses(client, { ...params, page: params.page! - 1 })
|
||||
: null,
|
||||
next: response.json?.length
|
||||
? () => paginatedPleromaStatuses(client, { ...params, page: (params.page || 0) + 1 })
|
||||
: null,
|
||||
items: v.parse(filteredArray(statusSchema), response.json),
|
||||
partial: response.status === 206,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const admin = (client: PlApiBaseClient) => {
|
||||
@ -1189,8 +1186,7 @@ const admin = (client: PlApiBaseClient) => {
|
||||
|
||||
const items = v.parse(filteredArray(adminAnnouncementSchema), response.json);
|
||||
|
||||
return {
|
||||
previous: null,
|
||||
return new PaginatedResponse(items, {
|
||||
next: items.length
|
||||
? () =>
|
||||
category.announcements.getAnnouncements({
|
||||
@ -1198,9 +1194,8 @@ const admin = (client: PlApiBaseClient) => {
|
||||
offset: (params?.offset || 0) + items.length,
|
||||
})
|
||||
: null,
|
||||
items,
|
||||
partial: false,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1336,7 +1331,7 @@ const admin = (client: PlApiBaseClient) => {
|
||||
|
||||
const items = v.parse(filteredArray(adminModerationLogEntrySchema), response.json.items);
|
||||
|
||||
return {
|
||||
return new PaginatedResponse(items, {
|
||||
previous:
|
||||
params.page && params.page > 1
|
||||
? () => category.moderationLog.getModerationLog({ ...params, page: params.page! - 1 })
|
||||
@ -1349,9 +1344,8 @@ const admin = (client: PlApiBaseClient) => {
|
||||
page: (params.page || 1) + 1,
|
||||
})
|
||||
: null,
|
||||
items,
|
||||
partial: response.status === 206,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@ -4,9 +4,9 @@ import { accountSchema, groupedNotificationsResultsSchema } from '@/entities';
|
||||
import { filteredArray } from '@/entities/utils';
|
||||
|
||||
import { type RequestMeta } from '../request';
|
||||
import { PaginatedResponse } from '../responses';
|
||||
import { pick, omit } from '../utils';
|
||||
|
||||
import type { PaginatedResponse } from '../responses';
|
||||
import type { notifications } from './notifications';
|
||||
import type { PlApiBaseClient } from '@/client-base';
|
||||
import type {
|
||||
@ -158,12 +158,11 @@ const groupedNotifications = (
|
||||
|
||||
const response = await client.request(`/api/v1/notifications/${groupKey}`);
|
||||
|
||||
return groupNotifications({
|
||||
previous: null,
|
||||
next: null,
|
||||
items: [response.json],
|
||||
partial: false,
|
||||
}).items;
|
||||
return groupNotifications(
|
||||
new PaginatedResponse([response.json], {
|
||||
partial: false,
|
||||
}),
|
||||
).items;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -13,8 +13,8 @@ import { filteredArray } from '@/entities/utils';
|
||||
|
||||
import { GOTOSOCIAL, ICESHRIMP_NET, MITRA, PIXELFED, PLEROMA } from '../features';
|
||||
import { getNextLink, getPrevLink } from '../request';
|
||||
import { PaginatedResponse } from '../responses';
|
||||
|
||||
import type { PaginatedResponse } from '../responses';
|
||||
import type { accounts } from './accounts';
|
||||
import type { PlApiBaseClient } from '@/client-base';
|
||||
import type { Account } from '@/entities';
|
||||
@ -44,12 +44,11 @@ const paginatedIceshrimpAccountsList = async <T>(
|
||||
const prevLink = getPrevLink(response);
|
||||
const nextLink = getNextLink(response);
|
||||
|
||||
return {
|
||||
return new PaginatedResponse(items, {
|
||||
previous: prevLink ? () => paginatedIceshrimpAccountsList(client, prevLink, fn) : null,
|
||||
next: nextLink ? () => paginatedIceshrimpAccountsList(client, nextLink, fn) : null,
|
||||
items,
|
||||
partial: response.status === 206,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const myAccount = (client: PlApiBaseClient & { accounts: ReturnType<typeof accounts> }) => ({
|
||||
|
||||
@ -1,12 +1,41 @@
|
||||
/**
|
||||
* @category Utils
|
||||
*/
|
||||
interface PaginatedResponse<T, IsArray extends boolean = true> {
|
||||
previous: (() => Promise<PaginatedResponse<T, IsArray>>) | null;
|
||||
next: (() => Promise<PaginatedResponse<T, IsArray>>) | null;
|
||||
class PaginatedResponse<T, IsArray extends boolean = true> {
|
||||
items: IsArray extends true ? Array<T> : T;
|
||||
partial: boolean;
|
||||
total?: number;
|
||||
declare previous: (() => Promise<PaginatedResponse<T, IsArray>>) | null;
|
||||
declare next: (() => Promise<PaginatedResponse<T, IsArray>>) | null;
|
||||
declare total: number | undefined;
|
||||
declare partial: boolean;
|
||||
|
||||
constructor(
|
||||
items: IsArray extends true ? Array<T> : T,
|
||||
info: Partial<Omit<PaginatedResponse<T, IsArray>, 'items'>> = {},
|
||||
) {
|
||||
this.items = items;
|
||||
Object.defineProperties(this, {
|
||||
items: { value: items, writable: true, enumerable: true, configurable: true },
|
||||
previous: {
|
||||
value: info.previous ?? null,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
next: {
|
||||
value: info.next ?? null,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
total: { value: info.total, writable: true, enumerable: false, configurable: true },
|
||||
partial: {
|
||||
value: info.partial ?? false,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type { PaginatedResponse };
|
||||
export { PaginatedResponse };
|
||||
|
||||
@ -67,10 +67,7 @@ const useNotificationList = (
|
||||
exclude_types: params.excludeTypes,
|
||||
})
|
||||
).then(importNotifications),
|
||||
initialPageParam: { previous: null, next: null } as Pick<
|
||||
PaginatedResponse<BaseNotification>,
|
||||
'previous' | 'next'
|
||||
>,
|
||||
initialPageParam: { next: null as (() => Promise<PaginatedResponse<BaseNotification>>) | null },
|
||||
getNextPageParam: (response) => response,
|
||||
},
|
||||
queryClient,
|
||||
@ -94,10 +91,7 @@ const prefetchNotifications = (client: PlApiClient, params: UseNotificationParam
|
||||
exclude_types: params.excludeTypes,
|
||||
})
|
||||
.then(importNotifications),
|
||||
initialPageParam: { previous: null, next: null } as Pick<
|
||||
PaginatedResponse<BaseNotification>,
|
||||
'previous' | 'next'
|
||||
>,
|
||||
initialPageParam: { next: null as (() => Promise<PaginatedResponse<BaseNotification>>) | null },
|
||||
});
|
||||
|
||||
export { useNotificationList, prefetchNotifications };
|
||||
|
||||
Reference in New Issue
Block a user