feat: useReplaceableEvent

This commit is contained in:
Shusui MOYATANI
2024-02-13 23:04:52 +09:00
parent 5e8ce8e48a
commit db188e6d27
2 changed files with 86 additions and 0 deletions

View File

@@ -19,6 +19,11 @@ export type ReactionsTask = { type: 'Reactions'; mentionedEventId: string };
export type ZapReceiptsTask = { type: 'ZapReceipts'; mentionedEventId: string }; export type ZapReceiptsTask = { type: 'ZapReceipts'; mentionedEventId: string };
export type RepostsTask = { type: 'Reposts'; mentionedEventId: string }; export type RepostsTask = { type: 'Reposts'; mentionedEventId: string };
export type FollowingsTask = { type: 'Followings'; pubkey: string }; export type FollowingsTask = { type: 'Followings'; pubkey: string };
export type ReplaceableEventTask = {
type: 'ReplaceableEvent';
kind: number;
author: string;
};
export type ParameterizedReplaceableEventTask = { export type ParameterizedReplaceableEventTask = {
type: 'ParameterizedReplaceableEvent'; type: 'ParameterizedReplaceableEvent';
kind: number; kind: number;
@@ -33,6 +38,7 @@ export type TaskArgs = [
ReactionsTask, ReactionsTask,
RepostsTask, RepostsTask,
ZapReceiptsTask, ZapReceiptsTask,
ReplaceableEventTask,
ParameterizedReplaceableEventTask, ParameterizedReplaceableEventTask,
]; ];
@@ -69,6 +75,9 @@ setInterval(() => {
setActiveBatchSubscriptions(count); setActiveBatchSubscriptions(count);
}, 1000); }, 1000);
const keyForReplaceableEvent = ({ kind, author }: { kind: number; author: string }) =>
`${kind}:${author}`;
const keyForParameterizedReplaceableEvent = ({ const keyForParameterizedReplaceableEvent = ({
kind, kind,
author, author,
@@ -164,6 +173,20 @@ export const tasksRequestBuilder = (tasks: BatchedEventsTask[]) => {
filtersBuilder: (ids) => [{ kinds: [Kind.Zap], '#e': ids }], filtersBuilder: (ids) => [{ kinds: [Kind.Zap], '#e': ids }],
eventKeyExtractor: (ev) => genericEvent(ev).lastTaggedEventId(), eventKeyExtractor: (ev) => genericEvent(ev).lastTaggedEventId(),
}); });
const replaceableEventsTasks = createTasks<ReplaceableEventTask>({
keyExtractor: keyForReplaceableEvent,
filtersBuilder: (keys) => {
const result: Filter[] = [];
keys.forEach((key) => {
const task = replaceableEventsTasks.tasks.get(key)?.[0];
if (task == null) return;
const { kind, author } = task.req;
result.push({ kinds: [kind], authors: [author] });
});
return result;
},
eventKeyExtractor: (ev) => keyForReplaceableEvent({ kind: ev.kind, author: ev.pubkey }),
});
const parameterizedReplaceableEventsTasks = createTasks<ParameterizedReplaceableEventTask>({ const parameterizedReplaceableEventsTasks = createTasks<ParameterizedReplaceableEventTask>({
keyExtractor: keyForParameterizedReplaceableEvent, keyExtractor: keyForParameterizedReplaceableEvent,
filtersBuilder: (keys) => { filtersBuilder: (keys) => {
@@ -200,6 +223,8 @@ export const tasksRequestBuilder = (tasks: BatchedEventsTask[]) => {
reactionsTasks.add(task); reactionsTasks.add(task);
} else if (isBatchedEventsTaskOf<ZapReceiptsTask>('ZapReceipts')(task)) { } else if (isBatchedEventsTaskOf<ZapReceiptsTask>('ZapReceipts')(task)) {
zapReceiptsTasks.add(task); zapReceiptsTasks.add(task);
} else if (isBatchedEventsTaskOf<ReplaceableEventTask>('ReplaceableEvent')(task)) {
replaceableEventsTasks.add(task);
} else if ( } else if (
isBatchedEventsTaskOf<ParameterizedReplaceableEventTask>('ParameterizedReplaceableEvent')( isBatchedEventsTaskOf<ParameterizedReplaceableEventTask>('ParameterizedReplaceableEvent')(
task, task,
@@ -218,6 +243,7 @@ export const tasksRequestBuilder = (tasks: BatchedEventsTask[]) => {
...repostsTasks.buildFilter(), ...repostsTasks.buildFilter(),
...reactionsTasks.buildFilter(), ...reactionsTasks.buildFilter(),
...zapReceiptsTasks.buildFilter(), ...zapReceiptsTasks.buildFilter(),
...replaceableEventsTasks.buildFilter(),
...parameterizedReplaceableEventsTasks.buildFilter(), ...parameterizedReplaceableEventsTasks.buildFilter(),
]; ];
@@ -237,6 +263,9 @@ export const tasksRequestBuilder = (tasks: BatchedEventsTask[]) => {
if (event.kind === Kind.Zap) { if (event.kind === Kind.Zap) {
if (zapReceiptsTasks.resolve(event)) return; if (zapReceiptsTasks.resolve(event)) return;
} }
if (Kind.isReplaceableKind(event.kind)) {
if (replaceableEventsTasks.resolve(event)) return;
}
if (Kind.isParameterizedReplaceableKind(event.kind)) { if (Kind.isParameterizedReplaceableKind(event.kind)) {
if (parameterizedReplaceableEventsTasks.resolve(event)) return; if (parameterizedReplaceableEventsTasks.resolve(event)) return;
} }
@@ -255,6 +284,7 @@ export const tasksRequestBuilder = (tasks: BatchedEventsTask[]) => {
repostsTasks, repostsTasks,
reactionsTasks, reactionsTasks,
zapReceiptsTasks, zapReceiptsTasks,
replaceableEventsTasks,
parameterizedReplaceableEventsTasks, parameterizedReplaceableEventsTasks,
}, },
add, add,

View File

@@ -0,0 +1,56 @@
import { createMemo } from 'solid-js';
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
import { type Event as NostrEvent } from 'nostr-tools/pure';
import { pickLatestEvent } from '@/nostr/event/comparator';
import { registerTask, BatchedEventsTask, ReplaceableEventTask } from '@/nostr/useBatchedEvents';
import timeout from '@/utils/timeout';
export type UseReplacableEventProps = {
kind: number;
author: string;
};
export type UseReplaceableEvent = {
event: () => NostrEvent | null;
query: CreateQueryResult<NostrEvent | null>;
};
const useReplaceableEvent = (
propsProvider: () => UseReplacableEventProps | null,
): UseReplaceableEvent => {
const queryClient = useQueryClient();
const props = createMemo(propsProvider);
const query = createQuery(() => ({
queryKey: ['useReplaceableEvent', props()] as const,
queryFn: ({ queryKey, signal }) => {
const [, currentProps] = queryKey;
if (currentProps == null) return null;
const { kind, author } = currentProps;
const task = new BatchedEventsTask<ReplaceableEventTask>({
type: 'ReplaceableEvent',
kind,
author,
});
const promise = task.firstEventPromise().catch(() => {
throw new Error(`event not found: ${kind}:${author}`);
});
task.onUpdate((events) => {
const latest = pickLatestEvent(events);
queryClient.setQueryData(queryKey, latest);
});
registerTask({ task, signal });
return timeout(15000, `useReplaceableEvent: ${kind}:${author}`)(promise);
},
staleTime: 5 * 60 * 1000, // 5 min
gcTime: 4 * 60 * 60 * 1000, // 4 hour
}));
const event = () => query.data ?? null;
return { event, query };
};
export default useReplaceableEvent;