mirror of
https://github.com/aljazceru/ditto.git
synced 2025-12-30 03:34:26 +01:00
Add Optimizer storage with EventSet
This commit is contained in:
109
src/utils/event-set.test.ts
Normal file
109
src/utils/event-set.test.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { assertEquals } from '@/deps-test.ts';
|
||||
|
||||
import { EventSet } from './event-set.ts';
|
||||
|
||||
Deno.test('EventSet', () => {
|
||||
const set = new EventSet();
|
||||
assertEquals(set.size, 0);
|
||||
|
||||
const event = { id: '1', kind: 0, pubkey: 'abc', content: '', created_at: 0, sig: '', tags: [] };
|
||||
set.add(event);
|
||||
assertEquals(set.size, 1);
|
||||
assertEquals(set.has(event), true);
|
||||
|
||||
set.add(event);
|
||||
assertEquals(set.size, 1);
|
||||
assertEquals(set.has(event), true);
|
||||
|
||||
set.delete(event);
|
||||
assertEquals(set.size, 0);
|
||||
assertEquals(set.has(event), false);
|
||||
|
||||
set.delete(event);
|
||||
assertEquals(set.size, 0);
|
||||
assertEquals(set.has(event), false);
|
||||
|
||||
set.add(event);
|
||||
assertEquals(set.size, 1);
|
||||
assertEquals(set.has(event), true);
|
||||
|
||||
set.clear();
|
||||
assertEquals(set.size, 0);
|
||||
assertEquals(set.has(event), false);
|
||||
});
|
||||
|
||||
Deno.test('EventSet.add (replaceable)', () => {
|
||||
const event0 = { id: '1', kind: 0, pubkey: 'abc', content: '', created_at: 0, sig: '', tags: [] };
|
||||
const event1 = { id: '2', kind: 0, pubkey: 'abc', content: '', created_at: 1, sig: '', tags: [] };
|
||||
const event2 = { id: '3', kind: 0, pubkey: 'abc', content: '', created_at: 2, sig: '', tags: [] };
|
||||
|
||||
const set = new EventSet();
|
||||
set.add(event0);
|
||||
assertEquals(set.size, 1);
|
||||
assertEquals(set.has(event0), true);
|
||||
|
||||
set.add(event1);
|
||||
assertEquals(set.size, 1);
|
||||
assertEquals(set.has(event0), false);
|
||||
assertEquals(set.has(event1), true);
|
||||
|
||||
set.add(event2);
|
||||
assertEquals(set.size, 1);
|
||||
assertEquals(set.has(event0), false);
|
||||
assertEquals(set.has(event1), false);
|
||||
assertEquals(set.has(event2), true);
|
||||
});
|
||||
|
||||
Deno.test('EventSet.add (parameterized)', () => {
|
||||
const event0 = { id: '1', kind: 30000, pubkey: 'abc', content: '', created_at: 0, sig: '', tags: [['d', 'a']] };
|
||||
const event1 = { id: '2', kind: 30000, pubkey: 'abc', content: '', created_at: 1, sig: '', tags: [['d', 'a']] };
|
||||
const event2 = { id: '3', kind: 30000, pubkey: 'abc', content: '', created_at: 2, sig: '', tags: [['d', 'a']] };
|
||||
|
||||
const set = new EventSet();
|
||||
set.add(event0);
|
||||
assertEquals(set.size, 1);
|
||||
assertEquals(set.has(event0), true);
|
||||
|
||||
set.add(event1);
|
||||
assertEquals(set.size, 1);
|
||||
assertEquals(set.has(event0), false);
|
||||
assertEquals(set.has(event1), true);
|
||||
|
||||
set.add(event2);
|
||||
assertEquals(set.size, 1);
|
||||
assertEquals(set.has(event0), false);
|
||||
assertEquals(set.has(event1), false);
|
||||
assertEquals(set.has(event2), true);
|
||||
});
|
||||
|
||||
Deno.test('EventSet.eventReplaces', () => {
|
||||
const event0 = { id: '1', kind: 0, pubkey: 'abc', content: '', created_at: 0, sig: '', tags: [] };
|
||||
const event1 = { id: '2', kind: 0, pubkey: 'abc', content: '', created_at: 1, sig: '', tags: [] };
|
||||
const event2 = { id: '3', kind: 0, pubkey: 'abc', content: '', created_at: 2, sig: '', tags: [] };
|
||||
const event3 = { id: '4', kind: 0, pubkey: 'def', content: '', created_at: 0, sig: '', tags: [] };
|
||||
|
||||
assertEquals(EventSet.eventReplaces(event1, event0), true);
|
||||
assertEquals(EventSet.eventReplaces(event2, event0), true);
|
||||
assertEquals(EventSet.eventReplaces(event2, event1), true);
|
||||
|
||||
assertEquals(EventSet.eventReplaces(event0, event1), false);
|
||||
assertEquals(EventSet.eventReplaces(event0, event2), false);
|
||||
assertEquals(EventSet.eventReplaces(event1, event2), false);
|
||||
|
||||
assertEquals(EventSet.eventReplaces(event3, event1), false);
|
||||
assertEquals(EventSet.eventReplaces(event1, event3), false);
|
||||
});
|
||||
|
||||
Deno.test('EventSet.eventReplaces (parameterized)', () => {
|
||||
const event0 = { id: '1', kind: 30000, pubkey: 'abc', content: '', created_at: 0, sig: '', tags: [['d', 'a']] };
|
||||
const event1 = { id: '2', kind: 30000, pubkey: 'abc', content: '', created_at: 1, sig: '', tags: [['d', 'a']] };
|
||||
const event2 = { id: '3', kind: 30000, pubkey: 'abc', content: '', created_at: 2, sig: '', tags: [['d', 'a']] };
|
||||
|
||||
assertEquals(EventSet.eventReplaces(event1, event0), true);
|
||||
assertEquals(EventSet.eventReplaces(event2, event0), true);
|
||||
assertEquals(EventSet.eventReplaces(event2, event1), true);
|
||||
|
||||
assertEquals(EventSet.eventReplaces(event0, event1), false);
|
||||
assertEquals(EventSet.eventReplaces(event0, event2), false);
|
||||
assertEquals(EventSet.eventReplaces(event1, event2), false);
|
||||
});
|
||||
77
src/utils/event-set.ts
Normal file
77
src/utils/event-set.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { type Event } from '@/deps.ts';
|
||||
import { isParameterizedReplaceableKind, isReplaceableKind } from '@/kinds.ts';
|
||||
|
||||
/** In-memory store for Nostr events with replaceable event functionality. */
|
||||
class EventSet<E extends Event = Event> implements Set<E> {
|
||||
#map = new Map<string, E>();
|
||||
|
||||
get size() {
|
||||
return this.#map.size;
|
||||
}
|
||||
|
||||
add(event: E): this {
|
||||
if (isReplaceableKind(event.kind) || isParameterizedReplaceableKind(event.kind)) {
|
||||
for (const e of this.values()) {
|
||||
if (EventSet.eventReplaces(event, e)) {
|
||||
this.delete(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.#map.set(event.id, event);
|
||||
return this;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.#map.clear();
|
||||
}
|
||||
|
||||
delete(event: E): boolean {
|
||||
return this.#map.delete(event.id);
|
||||
}
|
||||
|
||||
forEach(callbackfn: (event: E, key: E, set: Set<E>) => void, thisArg?: any): void {
|
||||
return this.#map.forEach((event, _id) => callbackfn(event, event, this), thisArg);
|
||||
}
|
||||
|
||||
has(event: E): boolean {
|
||||
return this.#map.has(event.id);
|
||||
}
|
||||
|
||||
*entries(): IterableIterator<[E, E]> {
|
||||
for (const event of this.#map.values()) {
|
||||
yield [event, event];
|
||||
}
|
||||
}
|
||||
|
||||
keys(): IterableIterator<E> {
|
||||
return this.#map.values();
|
||||
}
|
||||
|
||||
values(): IterableIterator<E> {
|
||||
return this.#map.values();
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<E> {
|
||||
return this.#map.values();
|
||||
}
|
||||
|
||||
[Symbol.toStringTag]: string = 'EventSet';
|
||||
|
||||
/** Returns true if both events are replaceable, belong to the same pubkey (and `d` tag, for parameterized events), and the first event is newer than the second one. */
|
||||
static eventReplaces(event: Event, event2: Event): boolean {
|
||||
if (isReplaceableKind(event.kind)) {
|
||||
return event.kind === event2.kind && event.pubkey === event2.pubkey && event.created_at > event2.created_at;
|
||||
} else if (isParameterizedReplaceableKind(event.kind)) {
|
||||
const d = event.tags.find(([name]) => name === 'd')?.[1] || '';
|
||||
const d2 = event2.tags.find(([name]) => name === 'd')?.[1] || '';
|
||||
|
||||
return event.kind === event2.kind &&
|
||||
event.pubkey === event2.pubkey &&
|
||||
d === d2 &&
|
||||
event.created_at > event2.created_at;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export { EventSet };
|
||||
Reference in New Issue
Block a user