ditto/src/filter.ts

97 lines
2.9 KiB
TypeScript
Raw Normal View History

import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
import stringifyStable from 'fast-stable-stringify';
2024-04-23 20:03:20 +00:00
import { z } from 'zod';
import { isReplaceableKind } from '@/kinds.ts';
2023-08-25 18:38:21 +00:00
2023-12-28 05:55:42 +00:00
/** Microfilter to get one specific event by ID. */
type IdMicrofilter = { ids: [NostrEvent['id']] };
2023-12-28 05:55:42 +00:00
/** Microfilter to get an author. */
type AuthorMicrofilter = { kinds: [0]; authors: [NostrEvent['pubkey']] };
2023-12-22 01:10:42 +00:00
/** Filter to get one specific event. */
2023-12-28 05:55:42 +00:00
type MicroFilter = IdMicrofilter | AuthorMicrofilter;
2023-12-22 01:10:42 +00:00
/** Get deterministic ID for a microfilter. */
function getFilterId(filter: MicroFilter): string {
if ('ids' in filter) {
return stringifyStable({ ids: [filter.ids[0]] });
2023-12-22 01:10:42 +00:00
} else {
return stringifyStable({
kinds: [filter.kinds[0]],
authors: [filter.authors[0]],
});
}
}
/** Get a microfilter from a Nostr event. */
function eventToMicroFilter(event: NostrEvent): MicroFilter {
2023-12-28 05:22:24 +00:00
const [microfilter] = getMicroFilters(event);
return microfilter;
}
/** Get all the microfilters for an event, in order of priority. */
function getMicroFilters(event: NostrEvent): MicroFilter[] {
2023-12-28 05:22:24 +00:00
const microfilters: MicroFilter[] = [];
2023-12-22 01:10:42 +00:00
if (event.kind === 0) {
2023-12-28 05:22:24 +00:00
microfilters.push({ kinds: [0], authors: [event.pubkey] });
2023-12-22 01:10:42 +00:00
}
2023-12-28 05:22:24 +00:00
microfilters.push({ ids: [event.id] });
return microfilters;
2023-12-22 01:10:42 +00:00
}
2023-12-28 04:49:35 +00:00
/** Microfilter schema. */
const microFilterSchema = z.union([
z.object({ ids: z.tuple([n.id()]) }).strict(),
z.object({ kinds: z.tuple([z.literal(0)]), authors: z.tuple([n.id()]) }).strict(),
2023-12-28 04:49:35 +00:00
]);
/** Checks whether the filter is a microfilter. */
function isMicrofilter(filter: NostrFilter): filter is MicroFilter {
2023-12-28 04:49:35 +00:00
return microFilterSchema.safeParse(filter).success;
}
/** Calculate the intrinsic limit of a filter. */
function getFilterLimit(filter: NostrFilter): number {
if (filter.ids && !filter.ids.length) return 0;
if (filter.kinds && !filter.kinds.length) return 0;
if (filter.authors && !filter.authors.length) return 0;
return Math.min(
Math.max(0, filter.limit ?? Infinity),
filter.ids?.length ?? Infinity,
filter.authors?.length &&
filter.kinds?.every((kind) => isReplaceableKind(kind))
? filter.authors.length * filter.kinds.length
: Infinity,
);
}
/** Returns true if the filter could potentially return any stored events at all. */
function canFilter(filter: NostrFilter): boolean {
return getFilterLimit(filter) > 0;
}
2024-01-04 00:15:28 +00:00
/** Normalize the `limit` of each filter, and remove filters that can't produce any events. */
function normalizeFilters<F extends NostrFilter>(filters: F[]): F[] {
2024-01-04 00:15:28 +00:00
return filters.reduce<F[]>((acc, filter) => {
const limit = getFilterLimit(filter);
if (limit > 0) {
acc.push(limit === Infinity ? filter : { ...filter, limit });
}
return acc;
}, []);
}
2023-12-22 01:10:42 +00:00
export {
2023-12-28 05:55:42 +00:00
type AuthorMicrofilter,
canFilter,
2023-12-22 01:10:42 +00:00
eventToMicroFilter,
getFilterId,
getFilterLimit,
2023-12-28 05:22:24 +00:00
getMicroFilters,
2023-12-28 05:55:42 +00:00
type IdMicrofilter,
2023-12-28 04:49:35 +00:00
isMicrofilter,
2023-12-22 01:10:42 +00:00
type MicroFilter,
2024-01-04 00:15:28 +00:00
normalizeFilters,
2023-12-22 01:10:42 +00:00
};