mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-17 05:54:19 +01:00
fix: avoid updating cache if event is old
This commit is contained in:
132
src/nostr/event/comparator.test.ts
Normal file
132
src/nostr/event/comparator.test.ts
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
|
import { Event as NostrEvent } from 'nostr-tools';
|
||||||
|
import { describe, it } from 'vitest';
|
||||||
|
|
||||||
|
import { compareEvents, pickLatestEvent } from '@/nostr/event/comparator';
|
||||||
|
|
||||||
|
describe('compareEvents', () => {
|
||||||
|
it('should return negative value if first event was made earlier than second one', () => {
|
||||||
|
const result = compareEvents(
|
||||||
|
{
|
||||||
|
id: 'b39d39a256ea0824436f1604830030c78f8a15c42b97036fe68124edc3452cc5',
|
||||||
|
pubkey: '6e62e578bdf608e250e93c25dc0cbadbda8db17e6fc3a28cdce8a2f56db7d106',
|
||||||
|
created_at: 1696169670,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'hello',
|
||||||
|
sig: '90787036c2cdb31125b7b6ef0ca746458d68e0a1d151243a7f29c7272e7be7ec14a72e425e2cf1cb0d9552e3d5acf97d20d4445ba71a710c5354153eeb9848ae',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ec6a3a0a1061a45502b66df8fa4f3454a32fe11a6ce79bfc4b2affafa0346da9',
|
||||||
|
pubkey: '6e62e578bdf608e250e93c25dc0cbadbda8db17e6fc3a28cdce8a2f56db7d106',
|
||||||
|
created_at: 1696169675,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'hello',
|
||||||
|
sig: '65ac58f314c99274304c540221e9ff603c26472f91cbb59bf5203e397a686917bb777c6e4c7ac43b27b0b519fc490e91b383d22b9ee8f1efc88175b2c6108c79',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert(result < 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return positive number if first event was made later than second one', () => {
|
||||||
|
const result = compareEvents(
|
||||||
|
{
|
||||||
|
id: 'ec6a3a0a1061a45502b66df8fa4f3454a32fe11a6ce79bfc4b2affafa0346da9',
|
||||||
|
pubkey: '6e62e578bdf608e250e93c25dc0cbadbda8db17e6fc3a28cdce8a2f56db7d106',
|
||||||
|
created_at: 1696169675,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'hello',
|
||||||
|
sig: '65ac58f314c99274304c540221e9ff603c26472f91cbb59bf5203e397a686917bb777c6e4c7ac43b27b0b519fc490e91b383d22b9ee8f1efc88175b2c6108c79',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'b39d39a256ea0824436f1604830030c78f8a15c42b97036fe68124edc3452cc5',
|
||||||
|
pubkey: '6e62e578bdf608e250e93c25dc0cbadbda8db17e6fc3a28cdce8a2f56db7d106',
|
||||||
|
created_at: 1696169670,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'hello',
|
||||||
|
sig: '90787036c2cdb31125b7b6ef0ca746458d68e0a1d151243a7f29c7272e7be7ec14a72e425e2cf1cb0d9552e3d5acf97d20d4445ba71a710c5354153eeb9848ae',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert(result > 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return negative number if both are made at the same time and first id is smaller than second one', () => {
|
||||||
|
const result = compareEvents(
|
||||||
|
{
|
||||||
|
id: '2b4f598e0586b8e54c7fecfd2a1686ef7d91f0e55236bf53d437b100a29c07ea',
|
||||||
|
pubkey: '6e62e578bdf608e250e93c25dc0cbadbda8db17e6fc3a28cdce8a2f56db7d106',
|
||||||
|
created_at: 1696169680,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'hello',
|
||||||
|
sig: 'a6d20521d9c141852d1a85bd139b13f26457d0cb878909fa92357e8d47b9bdfa3199fdb7c5a1cf1c1f9e38a0450e453577ae8325b5036fdf75ecfa7077e775d9',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '945a7a4df86e69eeafaba5b4025acb175f67e56a58ad3b8c8ce6532e6aad49a8',
|
||||||
|
pubkey: '6e62e578bdf608e250e93c25dc0cbadbda8db17e6fc3a28cdce8a2f56db7d106',
|
||||||
|
created_at: 1696169680,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'hello world',
|
||||||
|
sig: 'ca57fbea0f6a0e6a9a93920c204d4b829d0dcc71f0cff9b5e7d91dfe1af2245178d88c34acf9ff72d1afe2d799ef3f4b51a37a8ad2e8a27a9859e58fe984f6ee',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert(result < 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pickLatestEvent', () => {
|
||||||
|
it('should return the first event if there is only a single event', () => {
|
||||||
|
const events: NostrEvent[] = [
|
||||||
|
{
|
||||||
|
id: '2b4f598e0586b8e54c7fecfd2a1686ef7d91f0e55236bf53d437b100a29c07ea',
|
||||||
|
pubkey: '6e62e578bdf608e250e93c25dc0cbadbda8db17e6fc3a28cdce8a2f56db7d106',
|
||||||
|
created_at: 1696169680,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'hello',
|
||||||
|
sig: 'a6d20521d9c141852d1a85bd139b13f26457d0cb878909fa92357e8d47b9bdfa3199fdb7c5a1cf1c1f9e38a0450e453577ae8325b5036fdf75ecfa7077e775d9',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const result = pickLatestEvent(events);
|
||||||
|
assert.deepStrictEqual(result, events[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return latest event', () => {
|
||||||
|
const events: NostrEvent[] = [
|
||||||
|
{
|
||||||
|
id: 'b39d39a256ea0824436f1604830030c78f8a15c42b97036fe68124edc3452cc5',
|
||||||
|
pubkey: '6e62e578bdf608e250e93c25dc0cbadbda8db17e6fc3a28cdce8a2f56db7d106',
|
||||||
|
created_at: 1696169670,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'hello',
|
||||||
|
sig: '90787036c2cdb31125b7b6ef0ca746458d68e0a1d151243a7f29c7272e7be7ec14a72e425e2cf1cb0d9552e3d5acf97d20d4445ba71a710c5354153eeb9848ae',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ec6a3a0a1061a45502b66df8fa4f3454a32fe11a6ce79bfc4b2affafa0346da9',
|
||||||
|
pubkey: '6e62e578bdf608e250e93c25dc0cbadbda8db17e6fc3a28cdce8a2f56db7d106',
|
||||||
|
created_at: 1696169675,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'hello',
|
||||||
|
sig: '65ac58f314c99274304c540221e9ff603c26472f91cbb59bf5203e397a686917bb777c6e4c7ac43b27b0b519fc490e91b383d22b9ee8f1efc88175b2c6108c79',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2b4f598e0586b8e54c7fecfd2a1686ef7d91f0e55236bf53d437b100a29c07ea',
|
||||||
|
pubkey: '6e62e578bdf608e250e93c25dc0cbadbda8db17e6fc3a28cdce8a2f56db7d106',
|
||||||
|
created_at: 1696169680,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'hello',
|
||||||
|
sig: 'a6d20521d9c141852d1a85bd139b13f26457d0cb878909fa92357e8d47b9bdfa3199fdb7c5a1cf1c1f9e38a0450e453577ae8325b5036fdf75ecfa7077e775d9',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const result = pickLatestEvent(events);
|
||||||
|
assert.deepStrictEqual(result, events[2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
22
src/nostr/event/comparator.ts
Normal file
22
src/nostr/event/comparator.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Event as NostrEvent } from 'nostr-tools';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compareEvents compares events by created_at and id.
|
||||||
|
*
|
||||||
|
* Comparison by id is defined in NIP-01 for parameterized replaceable events
|
||||||
|
* but it is used here to ensure consistent results for sorting.
|
||||||
|
*/
|
||||||
|
export const compareEvents = (a: NostrEvent, b: NostrEvent): number => {
|
||||||
|
const diff = a.created_at - b.created_at;
|
||||||
|
if (diff !== 0) return diff;
|
||||||
|
return a.id > b.id ? 1 : -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pickLatestEvent = (events: NostrEvent[]): NostrEvent | undefined => {
|
||||||
|
if (events.length === 0) return undefined;
|
||||||
|
if (events.length === 1) return events[0];
|
||||||
|
return events.reduce((a, b) => (compareEvents(a, b) > 0 ? a : b));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sortEvents = (events: NostrEvent[]) =>
|
||||||
|
Array.from(events).sort((a, b) => -compareEvents(a, b));
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { QueryClient, QueryKey } from '@tanstack/solid-query';
|
import { QueryClient, QueryKey } from '@tanstack/solid-query';
|
||||||
|
import { uniqBy } from 'lodash';
|
||||||
import { Event as NostrEvent } from 'nostr-tools';
|
import { Event as NostrEvent } from 'nostr-tools';
|
||||||
|
|
||||||
import { BatchedEventsTask, pickLatestEvent, registerTask } from '@/nostr/useBatchedEvents';
|
import { pickLatestEvent, sortEvents } from '@/nostr/event/comparator';
|
||||||
|
import { BatchedEventsTask, registerTask } from '@/nostr/useBatchedEvents';
|
||||||
import timeout from '@/utils/timeout';
|
import timeout from '@/utils/timeout';
|
||||||
|
|
||||||
export const latestEventQuery =
|
export const latestEventQuery =
|
||||||
@@ -20,7 +22,11 @@ export const latestEventQuery =
|
|||||||
});
|
});
|
||||||
task.onUpdate((events) => {
|
task.onUpdate((events) => {
|
||||||
const latest = pickLatestEvent(events);
|
const latest = pickLatestEvent(events);
|
||||||
queryClient.setQueryData(queryKey, latest);
|
queryClient.setQueryData(queryKey, (prev: NostrEvent | undefined) =>
|
||||||
|
prev == null || (latest != null && latest.created_at > prev.created_at)
|
||||||
|
? latest
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
registerTask({ task, signal });
|
registerTask({ task, signal });
|
||||||
return timeout(15000, `${JSON.stringify(queryKey)}`)(promise);
|
return timeout(15000, `${JSON.stringify(queryKey)}`)(promise);
|
||||||
@@ -39,7 +45,12 @@ export const eventsQuery =
|
|||||||
if (task == null) return Promise.resolve([]);
|
if (task == null) return Promise.resolve([]);
|
||||||
const promise = task.toUpdatePromise().catch(() => []);
|
const promise = task.toUpdatePromise().catch(() => []);
|
||||||
task.onUpdate((events) => {
|
task.onUpdate((events) => {
|
||||||
queryClient.setQueryData(queryKey, events);
|
// TODO consider kind:5 deletion
|
||||||
|
queryClient.setQueryData(queryKey, (prev: NostrEvent[] | undefined) => {
|
||||||
|
if (prev == null) return events;
|
||||||
|
const deduped = uniqBy([...prev, ...events], (e) => e.id);
|
||||||
|
return sortEvents(deduped);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
registerTask({ task, signal });
|
registerTask({ task, signal });
|
||||||
return timeout(15000, `${JSON.stringify(queryKey)}`)(promise);
|
return timeout(15000, `${JSON.stringify(queryKey)}`)(promise);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { type Event as NostrEvent, type Filter, Kind } from 'nostr-tools';
|
import { type Event as NostrEvent, type Filter, Kind, utils } from 'nostr-tools';
|
||||||
|
|
||||||
import useConfig from '@/core/useConfig';
|
import useConfig from '@/core/useConfig';
|
||||||
import { genericEvent } from '@/nostr/event';
|
import { genericEvent } from '@/nostr/event';
|
||||||
|
import { pickLatestEvent } from '@/nostr/event/comparator';
|
||||||
import usePool from '@/nostr/usePool';
|
import usePool from '@/nostr/usePool';
|
||||||
import useStats from '@/nostr/useStats';
|
import useStats from '@/nostr/useStats';
|
||||||
import ObservableTask from '@/utils/batch/ObservableTask';
|
import ObservableTask from '@/utils/batch/ObservableTask';
|
||||||
@@ -29,19 +30,9 @@ type TaskArg =
|
|||||||
| RepostsTask
|
| RepostsTask
|
||||||
| ParameterizedReplaceableEventTask;
|
| 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[]> {
|
export class BatchedEventsTask extends ObservableTask<TaskArg, NostrEvent[]> {
|
||||||
addEvent(event: NostrEvent) {
|
addEvent(event: NostrEvent) {
|
||||||
this.updateWith((current) => [...(current ?? []), event]);
|
this.updateWith((current) => utils.insertEventIntoDescendingList(current ?? [], event));
|
||||||
}
|
}
|
||||||
|
|
||||||
firstEventPromise(): Promise<NostrEvent> {
|
firstEventPromise(): Promise<NostrEvent> {
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { createMemo, observable } from 'solid-js';
|
|||||||
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
||||||
import { Event as NostrEvent } from 'nostr-tools';
|
import { Event as NostrEvent } from 'nostr-tools';
|
||||||
|
|
||||||
import { registerTask, BatchedEventsTask, pickLatestEvent } from '@/nostr/useBatchedEvents';
|
import { pickLatestEvent } from '@/nostr/event/comparator';
|
||||||
|
import { registerTask, BatchedEventsTask } from '@/nostr/useBatchedEvents';
|
||||||
import timeout from '@/utils/timeout';
|
import timeout from '@/utils/timeout';
|
||||||
|
|
||||||
// Parameterized Replaceable Event
|
// Parameterized Replaceable Event
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import uniqBy from 'lodash/uniqBy';
|
|||||||
import { utils } from 'nostr-tools';
|
import { utils } from 'nostr-tools';
|
||||||
|
|
||||||
import useConfig from '@/core/useConfig';
|
import useConfig from '@/core/useConfig';
|
||||||
|
import { sortEvents } from '@/nostr/event/comparator';
|
||||||
import usePool from '@/nostr/usePool';
|
import usePool from '@/nostr/usePool';
|
||||||
import useStats from '@/nostr/useStats';
|
import useStats from '@/nostr/useStats';
|
||||||
|
|
||||||
@@ -29,9 +30,6 @@ export type UseSubscriptionProps = {
|
|||||||
debugId?: string;
|
debugId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortEvents = (events: NostrEvent[]) =>
|
|
||||||
Array.from(events).sort((a, b) => b.created_at - a.created_at);
|
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
const { setActiveSubscriptions } = useStats();
|
const { setActiveSubscriptions } = useStats();
|
||||||
|
|||||||
Reference in New Issue
Block a user