mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 22:44:26 +01:00
refactor
This commit is contained in:
46
src/nostr/query.ts
Normal file
46
src/nostr/query.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { QueryClient, QueryKey } from '@tanstack/solid-query';
|
||||
import { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import { BatchedEventsTask, pickLatestEvent, registerTask } from '@/nostr/useBatchedEvents';
|
||||
import timeout from '@/utils/timeout';
|
||||
|
||||
export const latestEventQuery =
|
||||
<K extends QueryKey>({
|
||||
taskProvider,
|
||||
queryClient,
|
||||
}: {
|
||||
taskProvider: (arg: K) => BatchedEventsTask | undefined | null;
|
||||
queryClient: QueryClient;
|
||||
}) =>
|
||||
({ queryKey, signal }: { queryKey: K; signal?: AbortSignal }): Promise<NostrEvent | null> => {
|
||||
const task = taskProvider(queryKey);
|
||||
if (task == null) return Promise.resolve(null);
|
||||
const promise = task.firstEventPromise().catch(() => {
|
||||
throw new Error(`event not found: ${JSON.stringify(queryKey)}`);
|
||||
});
|
||||
task.onUpdate((events) => {
|
||||
const latest = pickLatestEvent(events);
|
||||
queryClient.setQueryData(queryKey, latest);
|
||||
});
|
||||
registerTask({ task, signal });
|
||||
return timeout(15000, `${JSON.stringify(queryKey)}`)(promise);
|
||||
};
|
||||
|
||||
export const eventsQuery =
|
||||
<K extends QueryKey>({
|
||||
taskProvider,
|
||||
queryClient,
|
||||
}: {
|
||||
taskProvider: (arg: K) => BatchedEventsTask | undefined | null;
|
||||
queryClient: QueryClient;
|
||||
}) =>
|
||||
({ queryKey, signal }: { queryKey: K; signal?: AbortSignal }): Promise<NostrEvent[]> => {
|
||||
const task = taskProvider(queryKey);
|
||||
if (task == null) return Promise.resolve([]);
|
||||
const promise = task.toUpdatePromise().catch(() => []);
|
||||
task.onUpdate((events) => {
|
||||
queryClient.setQueryData(queryKey, events);
|
||||
});
|
||||
registerTask({ task, signal });
|
||||
return timeout(15000, `${JSON.stringify(queryKey)}`)(promise);
|
||||
};
|
||||
@@ -29,6 +29,16 @@ type TaskArg =
|
||||
| RepostsTask
|
||||
| ParameterizedReplaceableEventTask;
|
||||
|
||||
export const pickLatestEvent = (events: NostrEvent[]): NostrEvent | null => {
|
||||
if (events.length === 0) return null;
|
||||
return events.reduce((a, b) => {
|
||||
const diff = a.created_at - b.created_at;
|
||||
if (diff > 0) return a;
|
||||
if (diff < 0) return b;
|
||||
return a.id < b.id ? a : b;
|
||||
});
|
||||
};
|
||||
|
||||
export class BatchedEventsTask extends ObservableTask<TaskArg, NostrEvent[]> {
|
||||
addEvent(event: NostrEvent) {
|
||||
this.updateWith((current) => [...(current ?? []), event]);
|
||||
@@ -37,6 +47,14 @@ export class BatchedEventsTask extends ObservableTask<TaskArg, NostrEvent[]> {
|
||||
firstEventPromise(): Promise<NostrEvent> {
|
||||
return this.toUpdatePromise().then((events) => events[0]);
|
||||
}
|
||||
|
||||
latestEventPromise(): Promise<NostrEvent> {
|
||||
return this.toCompletePromise().then((events) => {
|
||||
const latest = pickLatestEvent(events);
|
||||
if (latest == null) throw new Error('event not found');
|
||||
return latest;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
@@ -219,13 +237,3 @@ export const registerTask = ({
|
||||
addTask(task);
|
||||
signal?.addEventListener('abort', () => removeTask(task));
|
||||
};
|
||||
|
||||
export const pickLatestEvent = (events: NostrEvent[]): NostrEvent | null => {
|
||||
if (events.length === 0) return null;
|
||||
return events.reduce((a, b) => {
|
||||
const diff = a.created_at - b.created_at;
|
||||
if (diff > 0) return a;
|
||||
if (diff < 0) return b;
|
||||
return a.id < b.id ? a : b;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createMemo, createSignal } from 'solid-js';
|
||||
import { createMemo } from 'solid-js';
|
||||
|
||||
import uniq from 'lodash/uniq';
|
||||
import { Kind } from 'nostr-tools';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createMemo, observable } from 'solid-js';
|
||||
import { createMemo } from 'solid-js';
|
||||
|
||||
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
||||
import { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import { genericEvent } from '@/nostr/event';
|
||||
import { registerTask, BatchedEventsTask, pickLatestEvent } from '@/nostr/useBatchedEvents';
|
||||
import timeout from '@/utils/timeout';
|
||||
import { latestEventQuery } from '@/nostr/query';
|
||||
import { BatchedEventsTask, registerTask } from '@/nostr/useBatchedEvents';
|
||||
|
||||
type Following = {
|
||||
pubkey: string;
|
||||
@@ -24,6 +24,15 @@ export type UseFollowings = {
|
||||
query: CreateQueryResult<NostrEvent | null>;
|
||||
};
|
||||
|
||||
export const fetchLatestFollowings = (
|
||||
{ pubkey }: UseFollowingsProps,
|
||||
signal?: AbortSignal,
|
||||
): Promise<NostrEvent> => {
|
||||
const task = new BatchedEventsTask({ type: 'Followings', pubkey });
|
||||
registerTask({ task, signal });
|
||||
return task.latestEventPromise();
|
||||
};
|
||||
|
||||
const useFollowings = (propsProvider: () => UseFollowingsProps | null): UseFollowings => {
|
||||
const queryClient = useQueryClient();
|
||||
const props = createMemo(propsProvider);
|
||||
@@ -31,22 +40,14 @@ const useFollowings = (propsProvider: () => UseFollowingsProps | null): UseFollo
|
||||
|
||||
const query = createQuery(
|
||||
genQueryKey,
|
||||
({ queryKey, signal }) => {
|
||||
console.debug('useFollowings');
|
||||
const [, currentProps] = queryKey;
|
||||
if (currentProps == null) return Promise.resolve(null);
|
||||
latestEventQuery({
|
||||
taskProvider: ([, currentProps]) => {
|
||||
if (currentProps == null) return null;
|
||||
const { pubkey } = currentProps;
|
||||
const task = new BatchedEventsTask({ type: 'Followings', pubkey });
|
||||
const promise = task.firstEventPromise().catch(() => {
|
||||
throw new Error(`followings not found: ${pubkey}`);
|
||||
});
|
||||
task.onUpdate((events) => {
|
||||
const latest = pickLatestEvent(events);
|
||||
queryClient.setQueryData(queryKey, latest);
|
||||
});
|
||||
registerTask({ task, signal });
|
||||
return timeout(15000, `useFollowings: ${pubkey}`)(promise);
|
||||
return new BatchedEventsTask({ type: 'Followings', pubkey });
|
||||
},
|
||||
queryClient,
|
||||
}),
|
||||
{
|
||||
staleTime: 5 * 60 * 1000, // 5 min
|
||||
cacheTime: 24 * 60 * 60 * 1000, // 24 hour
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { createMemo, observable } from 'solid-js';
|
||||
import { createMemo } from 'solid-js';
|
||||
|
||||
import {
|
||||
createQuery,
|
||||
useQueryClient,
|
||||
type CreateQueryResult,
|
||||
QueryClient,
|
||||
} from '@tanstack/solid-query';
|
||||
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
||||
import { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import { Profile, ProfileWithOtherProperties, safeParseProfile } from '@/nostr/event/Profile';
|
||||
import { BatchedEventsTask, pickLatestEvent, registerTask } from '@/nostr/useBatchedEvents';
|
||||
import timeout from '@/utils/timeout';
|
||||
import { latestEventQuery } from '@/nostr/query';
|
||||
import { BatchedEventsTask } from '@/nostr/useBatchedEvents';
|
||||
|
||||
export type UseProfileProps = {
|
||||
pubkey: string;
|
||||
@@ -40,21 +35,14 @@ const useProfile = (propsProvider: () => UseProfileProps | null): UseProfile =>
|
||||
|
||||
const query = createQuery(
|
||||
genQueryKey,
|
||||
({ queryKey, signal }) => {
|
||||
const [, currentProps] = queryKey;
|
||||
latestEventQuery({
|
||||
taskProvider: ([, currentProps]) => {
|
||||
if (currentProps == null) return null;
|
||||
const { pubkey } = currentProps;
|
||||
const task = new BatchedEventsTask({ type: 'Profile', pubkey });
|
||||
const promise = task.firstEventPromise().catch(() => {
|
||||
throw new Error(`profile not found: ${pubkey}`);
|
||||
});
|
||||
task.onUpdate((events) => {
|
||||
const latest = pickLatestEvent(events);
|
||||
queryClient.setQueryData(queryKey, latest);
|
||||
});
|
||||
registerTask({ task, signal });
|
||||
return timeout(3000, `useProfile: ${pubkey}`)(promise);
|
||||
return new BatchedEventsTask({ type: 'Profile', pubkey });
|
||||
},
|
||||
queryClient,
|
||||
}),
|
||||
{
|
||||
// Profiles are updated occasionally, so a short staleTime is used here.
|
||||
// cacheTime is long so that the user see profiles instantly.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createMemo, observable } from 'solid-js';
|
||||
import { createMemo } from 'solid-js';
|
||||
|
||||
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
||||
import { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import useConfig from '@/core/useConfig';
|
||||
import { registerTask, BatchedEventsTask } from '@/nostr/useBatchedEvents';
|
||||
import timeout from '@/utils/timeout';
|
||||
import { eventsQuery } from '@/nostr/query';
|
||||
import { BatchedEventsTask } from '@/nostr/useBatchedEvents';
|
||||
|
||||
export type UseReactionsProps = {
|
||||
eventId: string;
|
||||
@@ -30,18 +30,14 @@ const useReactions = (propsProvider: () => UseReactionsProps | null): UseReactio
|
||||
|
||||
const query = createQuery(
|
||||
genQueryKey,
|
||||
({ queryKey, signal }) => {
|
||||
const [, currentProps] = queryKey;
|
||||
if (currentProps == null) return [];
|
||||
eventsQuery({
|
||||
taskProvider: ([, currentProps]) => {
|
||||
if (currentProps == null) return null;
|
||||
const { eventId: mentionedEventId } = currentProps;
|
||||
const task = new BatchedEventsTask({ type: 'Reactions', mentionedEventId });
|
||||
const promise = task.toUpdatePromise().catch(() => []);
|
||||
task.onUpdate((events) => {
|
||||
queryClient.setQueryData(queryKey, events);
|
||||
});
|
||||
registerTask({ task, signal });
|
||||
return timeout(15000, `useReactions: ${mentionedEventId}`)(promise);
|
||||
return new BatchedEventsTask({ type: 'Reactions', mentionedEventId });
|
||||
},
|
||||
queryClient,
|
||||
}),
|
||||
{
|
||||
staleTime: 1 * 60 * 1000, // 1 min
|
||||
cacheTime: 4 * 60 * 60 * 1000, // 4 hour
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createMemo, observable } from 'solid-js';
|
||||
import { createMemo } from 'solid-js';
|
||||
|
||||
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
||||
import { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import useConfig from '@/core/useConfig';
|
||||
import { BatchedEventsTask, registerTask } from '@/nostr/useBatchedEvents';
|
||||
import timeout from '@/utils/timeout';
|
||||
import { eventsQuery } from '@/nostr/query';
|
||||
import { BatchedEventsTask } from '@/nostr/useBatchedEvents';
|
||||
|
||||
export type UseRepostsProps = {
|
||||
eventId: string;
|
||||
@@ -26,18 +26,14 @@ const useReposts = (propsProvider: () => UseRepostsProps): UseReposts => {
|
||||
|
||||
const query = createQuery(
|
||||
genQueryKey,
|
||||
({ queryKey, signal }) => {
|
||||
const [, currentProps] = queryKey;
|
||||
if (currentProps == null) return [];
|
||||
eventsQuery({
|
||||
taskProvider: ([, currentProps]) => {
|
||||
if (currentProps == null) return null;
|
||||
const { eventId: mentionedEventId } = currentProps;
|
||||
const task = new BatchedEventsTask({ type: 'Reposts', mentionedEventId });
|
||||
const promise = task.toUpdatePromise().catch(() => []);
|
||||
task.onUpdate((events) => {
|
||||
queryClient.setQueryData(queryKey, events);
|
||||
});
|
||||
registerTask({ task, signal });
|
||||
return timeout(15000, `useReposts: ${mentionedEventId}`)(promise);
|
||||
return new BatchedEventsTask({ type: 'Reposts', mentionedEventId });
|
||||
},
|
||||
queryClient,
|
||||
}),
|
||||
{
|
||||
staleTime: 1 * 60 * 1000, // 1 min
|
||||
cacheTime: 4 * 60 * 60 * 1000, // 4 hour
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
export class TimeoutError extends Error {}
|
||||
|
||||
const timeout =
|
||||
(ms: number, info?: string) =>
|
||||
<T>(promise: Promise<T>): Promise<T> => {
|
||||
const timeoutPromise = new Promise<T>((_resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
const message = info != null ? `TimeoutError: ${info}` : 'TimeoutError';
|
||||
reject(new Error(message));
|
||||
reject(new TimeoutError(message));
|
||||
}, ms);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user