Merge branch 'refactor-storages' into 'main'

First pass refactoring storages

See merge request soapbox-pub/ditto!203
This commit is contained in:
Alex Gleason 2024-05-01 20:27:08 +00:00
commit efd6e35065
29 changed files with 1680 additions and 141 deletions

View File

@ -1,6 +1,5 @@
{
"$schema": "https://deno.land/x/deno@v1.41.0/cli/schemas/config-file.v1.json",
"lock": false,
"tasks": {
"start": "deno run -A src/server.ts",
"dev": "deno run -A --watch src/server.ts",

1505
deno.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ import { Conf } from '@/config.ts';
import { getAuthor, getFollowedPubkeys } from '@/queries.ts';
import { booleanParamSchema, fileSchema } from '@/schema.ts';
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
import { eventsDB, searchStore } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { addTag, deleteTag, findReplyTag, getTagSet } from '@/tags.ts';
import { uploadFile } from '@/upload.ts';
import { nostrNow } from '@/utils.ts';
@ -92,12 +92,12 @@ const accountSearchController: AppController = async (c) => {
const [event, events] = await Promise.all([
lookupAccount(query),
searchStore.query([{ kinds: [0], search: query, limit: 20 }], { signal: c.req.raw.signal }),
Storages.search.query([{ kinds: [0], search: query, limit: 20 }], { signal: c.req.raw.signal }),
]);
const results = await hydrateEvents({
events: event ? [event, ...events] : events,
storage: eventsDB,
storage: Storages.db,
signal: c.req.raw.signal,
});
@ -143,7 +143,7 @@ const accountStatusesController: AppController = async (c) => {
const { signal } = c.req.raw;
if (pinned) {
const [pinEvent] = await eventsDB.query([{ kinds: [10001], authors: [pubkey], limit: 1 }], { signal });
const [pinEvent] = await Storages.db.query([{ kinds: [10001], authors: [pubkey], limit: 1 }], { signal });
if (pinEvent) {
const pinnedEventIds = getTagSet(pinEvent.tags, 'e');
return renderStatuses(c, [...pinnedEventIds].reverse());
@ -164,8 +164,8 @@ const accountStatusesController: AppController = async (c) => {
filter['#t'] = [tagged];
}
const events = await eventsDB.query([filter], { signal })
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }))
const events = await Storages.db.query([filter], { signal })
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }))
.then((events) => {
if (exclude_replies) {
return events.filter((event) => !findReplyTag(event.tags));
@ -306,7 +306,7 @@ const favouritesController: AppController = async (c) => {
const params = paginationSchema.parse(c.req.query());
const { signal } = c.req.raw;
const events7 = await eventsDB.query(
const events7 = await Storages.db.query(
[{ kinds: [7], authors: [pubkey], ...params }],
{ signal },
);
@ -315,8 +315,8 @@ const favouritesController: AppController = async (c) => {
.map((event) => event.tags.find((tag) => tag[0] === 'e')?.[1])
.filter((id): id is string => !!id);
const events1 = await eventsDB.query([{ kinds: [1], ids }], { signal })
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
const events1 = await Storages.db.query([{ kinds: [1], ids }], { signal })
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
const statuses = await Promise.all(events1.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })));
return paginated(c, events1, statuses);

View File

@ -3,7 +3,7 @@ import { z } from 'zod';
import { type AppController } from '@/app.ts';
import { Conf } from '@/config.ts';
import { booleanParamSchema } from '@/schema.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { renderAdminAccount } from '@/views/mastodon/admin-accounts.ts';
import { paginated, paginationSchema } from '@/utils/api.ts';
@ -41,9 +41,9 @@ const adminAccountsController: AppController = async (c) => {
const { since, until, limit } = paginationSchema.parse(c.req.query());
const { signal } = c.req.raw;
const events = await eventsDB.query([{ kinds: [30361], authors: [Conf.pubkey], since, until, limit }], { signal });
const events = await Storages.db.query([{ kinds: [30361], authors: [Conf.pubkey], since, until, limit }], { signal });
const pubkeys = events.map((event) => event.tags.find(([name]) => name === 'd')?.[1]!);
const authors = await eventsDB.query([{ kinds: [0], authors: pubkeys }], { signal });
const authors = await Storages.db.query([{ kinds: [0], authors: pubkeys }], { signal });
for (const event of events) {
const d = event.tags.find(([name]) => name === 'd')?.[1];

View File

@ -1,5 +1,5 @@
import { type AppController } from '@/app.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { getTagSet } from '@/tags.ts';
import { renderAccounts } from '@/views.ts';
@ -8,7 +8,7 @@ const blocksController: AppController = async (c) => {
const pubkey = c.get('pubkey')!;
const { signal } = c.req.raw;
const [event10000] = await eventsDB.query(
const [event10000] = await Storages.db.query(
[{ kinds: [10000], authors: [pubkey], limit: 1 }],
{ signal },
);

View File

@ -1,5 +1,5 @@
import { type AppController } from '@/app.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { getTagSet } from '@/tags.ts';
import { renderStatuses } from '@/views.ts';
@ -8,7 +8,7 @@ const bookmarksController: AppController = async (c) => {
const pubkey = c.get('pubkey')!;
const { signal } = c.req.raw;
const [event10003] = await eventsDB.query(
const [event10003] = await Storages.db.query(
[{ kinds: [10003], authors: [pubkey], limit: 1 }],
{ signal },
);

View File

@ -3,7 +3,7 @@ import { z } from 'zod';
import { AppController } from '@/app.ts';
import { Conf } from '@/config.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { AdminSigner } from '@/signers/AdminSigner.ts';
const relaySchema = z.object({
@ -15,7 +15,7 @@ const relaySchema = z.object({
type RelayEntity = z.infer<typeof relaySchema>;
export const adminRelaysController: AppController = async (c) => {
const [event] = await eventsDB.query([
const [event] = await Storages.db.query([
{ kinds: [10002], authors: [Conf.pubkey], limit: 1 },
]);
@ -36,7 +36,7 @@ export const adminSetRelaysController: AppController = async (c) => {
created_at: Math.floor(Date.now() / 1000),
});
await eventsDB.event(event);
await Storages.db.event(event);
return c.json(renderRelays(event));
};

View File

@ -1,13 +1,13 @@
import { type AppController } from '@/app.ts';
import { Conf } from '@/config.ts';
import { jsonServerMetaSchema } from '@/schemas/nostr.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
const instanceController: AppController = async (c) => {
const { host, protocol } = Conf.url;
const { signal } = c.req.raw;
const [event] = await eventsDB.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal });
const [event] = await Storages.db.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal });
const meta = jsonServerMetaSchema.parse(event?.content);
/** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */

View File

@ -1,5 +1,5 @@
import { type AppController } from '@/app.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { paginated, paginationSchema } from '@/utils/api.ts';
import { renderNotification } from '@/views/mastodon/notifications.ts';
@ -8,7 +8,7 @@ const notificationsController: AppController = async (c) => {
const { since, until } = paginationSchema.parse(c.req.query());
const { signal } = c.req.raw;
const events = await eventsDB.query(
const events = await Storages.db.query(
[{ kinds: [1], '#p': [pubkey], since, until }],
{ signal },
);

View File

@ -4,7 +4,7 @@ import { type AppController } from '@/app.ts';
import { Conf } from '@/config.ts';
import { configSchema, elixirTupleSchema, type PleromaConfig } from '@/schemas/pleroma-api.ts';
import { AdminSigner } from '@/signers/AdminSigner.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { createAdminEvent } from '@/utils/api.ts';
import { jsonSchema } from '@/schema.ts';
@ -66,7 +66,7 @@ const pleromaAdminDeleteStatusController: AppController = async (c) => {
async function getConfigs(signal: AbortSignal): Promise<PleromaConfig[]> {
const { pubkey } = Conf;
const [event] = await eventsDB.query([{
const [event] = await Storages.db.query([{
kinds: [30078],
authors: [pubkey],
'#d': ['pub.ditto.pleroma.config'],

View File

@ -5,7 +5,7 @@ import { z } from 'zod';
import { AppController } from '@/app.ts';
import { booleanParamSchema } from '@/schema.ts';
import { nostrIdSchema } from '@/schemas/nostr.ts';
import { searchStore } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { dedupeEvents } from '@/utils.ts';
import { nip05Cache } from '@/utils/nip05.ts';
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
@ -91,8 +91,8 @@ function searchEvents({ q, type, limit, account_id }: SearchQuery, signal: Abort
filter.authors = [account_id];
}
return searchStore.query([filter], { signal })
.then((events) => hydrateEvents({ events, storage: searchStore, signal }));
return Storages.search.query([filter], { signal })
.then((events) => hydrateEvents({ events, storage: Storages.search, signal }));
}
/** Get event kinds to search from `type` query param. */
@ -111,8 +111,8 @@ function typeToKinds(type: SearchQuery['type']): number[] {
async function lookupEvent(query: SearchQuery, signal: AbortSignal): Promise<NostrEvent | undefined> {
const filters = await getLookupFilters(query, signal);
return searchStore.query(filters, { limit: 1, signal })
.then((events) => hydrateEvents({ events, storage: searchStore, signal }))
return Storages.search.query(filters, { limit: 1, signal })
.then((events) => hydrateEvents({ events, storage: Storages.search, signal }))
.then(([event]) => event);
}

View File

@ -15,7 +15,7 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
import { getLnurl } from '@/utils/lnurl.ts';
import { nip05Cache } from '@/utils/nip05.ts';
import { asyncReplaceAll } from '@/utils/text.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { hydrateEvents } from '@/storages/hydrate.ts';
const createStatusSchema = z.object({
@ -137,7 +137,7 @@ const createStatusController: AppController = async (c) => {
if (data.quote_id) {
await hydrateEvents({
events: [event],
storage: eventsDB,
storage: Storages.db,
signal: c.req.raw.signal,
});
}
@ -242,7 +242,7 @@ const reblogStatusController: AppController = async (c) => {
await hydrateEvents({
events: [reblogEvent],
storage: eventsDB,
storage: Storages.db,
signal: signal,
});
@ -262,7 +262,7 @@ const unreblogStatusController: AppController = async (c) => {
if (!event) return c.json({ error: 'Event not found.' }, 404);
const filters: NostrFilter[] = [{ kinds: [6], authors: [pubkey], '#e': [event.id] }];
const [repostedEvent] = await eventsDB.query(filters, { limit: 1 });
const [repostedEvent] = await Storages.db.query(filters, { limit: 1 });
if (!repostedEvent) return c.json({ error: 'Event not found.' }, 404);
await createEvent({

View File

@ -10,7 +10,6 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
import { hydrateEvents } from '@/storages/hydrate.ts';
import { Storages } from '@/storages.ts';
import { UserStore } from '@/storages/UserStore.ts';
import { getAdminStore } from '@/storages/adminStore.ts';
const debug = Debug('ditto:streaming');
@ -69,11 +68,11 @@ const streamingController: AppController = (c) => {
const filter = await topicToFilter(stream, c.req.query(), pubkey);
if (!filter) return;
const store = pubkey ? new UserStore(pubkey, Storages.admin) : Storages.admin;
try {
for await (const msg of Storages.pubsub.req([filter], { signal: controller.signal })) {
if (msg[0] === 'EVENT') {
const store = new UserStore(pubkey as string, getAdminStore());
const [event] = await store.query([{ ids: [msg[2].id] }]);
if (!event) continue;

View File

@ -1,11 +1,11 @@
import { AppController } from '@/app.ts';
import { Conf } from '@/config.ts';
import { jsonServerMetaSchema } from '@/schemas/nostr.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
const relayInfoController: AppController = async (c) => {
const { signal } = c.req.raw;
const [event] = await eventsDB.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal });
const [event] = await Storages.db.query([{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], { signal });
const meta = jsonServerMetaSchema.parse(event?.content);
return c.json({

View File

@ -1,6 +1,5 @@
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
import { relayInfoController } from '@/controllers/nostr/relay-info.ts';
import { eventsDB } from '@/storages.ts';
import * as pipeline from '@/pipeline.ts';
import {
type ClientCLOSE,
@ -71,7 +70,7 @@ function connectStream(socket: WebSocket) {
controllers.get(subId)?.abort();
controllers.set(subId, controller);
for (const event of await eventsDB.query(filters, { limit: FILTER_LIMIT })) {
for (const event of await Storages.db.query(filters, { limit: FILTER_LIMIT })) {
send(['EVENT', subId, event]);
}
@ -115,7 +114,7 @@ function connectStream(socket: WebSocket) {
/** Handle COUNT. Return the number of events matching the filters. */
async function handleCount([_, subId, ...rest]: ClientCOUNT): Promise<void> {
const { count } = await eventsDB.count(prepareFilters(rest));
const { count } = await Storages.db.count(prepareFilters(rest));
send(['COUNT', subId, { count, approximate: false }]);
}

View File

@ -3,7 +3,7 @@ import { Conf } from '@/config.ts';
import { Debug } from '@/deps.ts';
import * as pipeline from '@/pipeline.ts';
import { AdminSigner } from '@/signers/AdminSigner.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
const debug = Debug('ditto:users');
@ -59,7 +59,7 @@ async function findUser(user: Partial<User>, signal?: AbortSignal): Promise<User
}
}
const [event] = await eventsDB.query([filter], { signal });
const [event] = await Storages.db.query([filter], { signal });
if (event) {
return {

View File

@ -1,16 +1,16 @@
import { AppMiddleware } from '@/app.ts';
import { UserStore } from '@/storages/UserStore.ts';
import { getAdminStore } from '@/storages/adminStore.ts';
import { Storages } from '@/storages.ts';
/** Store middleware. */
const storeMiddleware: AppMiddleware = async (c, next) => {
const pubkey = c.get('pubkey');
const adminStore = getAdminStore();
if (pubkey) {
const store = new UserStore(pubkey, adminStore);
const store = new UserStore(pubkey, Storages.admin);
c.set('store', store);
} else {
c.set('store', adminStore);
c.set('store', Storages.admin);
}
await next();
};

View File

@ -12,7 +12,7 @@ import { isEphemeralKind } from '@/kinds.ts';
import { DVM } from '@/pipeline/DVM.ts';
import { updateStats } from '@/stats.ts';
import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts';
import { cache, eventsDB, reqmeister, Storages } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { getTagSet } from '@/tags.ts';
import { eventAge, isRelay, nostrDate, nostrNow, parseNip05, Time } from '@/utils.ts';
import { fetchWorker } from '@/workers/fetch.ts';
@ -70,15 +70,15 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise<void
/** Encounter the event, and return whether it has already been encountered. */
async function encounterEvent(event: NostrEvent, signal: AbortSignal): Promise<boolean> {
const [existing] = await cache.query([{ ids: [event.id], limit: 1 }]);
cache.event(event);
reqmeister.event(event, { signal });
const [existing] = await Storages.cache.query([{ ids: [event.id], limit: 1 }]);
Storages.cache.event(event);
Storages.reqmeister.event(event, { signal });
return !!existing;
}
/** Hydrate the event with the user, if applicable. */
async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<void> {
await hydrateEvents({ events: [event], storage: eventsDB, signal });
await hydrateEvents({ events: [event], storage: Storages.db, signal });
const domain = await db
.selectFrom('pubkey_domains')
@ -93,7 +93,7 @@ async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<voi
async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<void> {
if (isEphemeralKind(event.kind)) return;
const [deletion] = await eventsDB.query(
const [deletion] = await Storages.db.query(
[{ kinds: [5], authors: [Conf.pubkey, event.pubkey], '#e': [event.id], limit: 1 }],
{ signal },
);
@ -102,7 +102,7 @@ async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<void
return Promise.reject(new RelayError('blocked', 'event was deleted'));
} else {
await Promise.all([
eventsDB.event(event, { signal }).catch(debug),
Storages.db.event(event, { signal }).catch(debug),
updateStats(event).catch(debug),
]);
}
@ -151,15 +151,15 @@ async function processDeletions(event: NostrEvent, signal: AbortSignal): Promise
const ids = getTagSet(event.tags, 'e');
if (event.pubkey === Conf.pubkey) {
await eventsDB.remove([{ ids: [...ids] }], { signal });
await Storages.db.remove([{ ids: [...ids] }], { signal });
} else {
const events = await eventsDB.query(
const events = await Storages.db.query(
[{ ids: [...ids], authors: [event.pubkey] }],
{ signal },
);
const deleteIds = events.map(({ id }) => id);
await eventsDB.remove([{ ids: deleteIds }], { signal });
await Storages.db.remove([{ ids: deleteIds }], { signal });
}
}
}
@ -202,14 +202,14 @@ function trackRelays(event: NostrEvent) {
/** Queue related events to fetch. */
async function fetchRelatedEvents(event: DittoEvent, signal: AbortSignal) {
if (!event.user) {
reqmeister.req({ kinds: [0], authors: [event.pubkey] }, { signal }).catch(() => {});
Storages.reqmeister.req({ kinds: [0], authors: [event.pubkey] }, { signal }).catch(() => {});
}
for (const [name, id, relay] of event.tags) {
if (name === 'e') {
const { count } = await cache.count([{ ids: [id] }]);
const { count } = await Storages.cache.count([{ ids: [id] }]);
if (!count) {
reqmeister.req({ ids: [id] }, { relays: [relay] }).catch(() => {});
Storages.reqmeister.req({ ids: [id] }, { relays: [relay] }).catch(() => {});
}
}
}

View File

@ -3,7 +3,7 @@ import { NIP05, NostrEvent } from '@nostrify/nostrify';
import { Conf } from '@/config.ts';
import * as pipeline from '@/pipeline.ts';
import { AdminSigner } from '@/signers/AdminSigner.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
export class DVM {
static async event(event: NostrEvent): Promise<void> {
@ -34,7 +34,7 @@ export class DVM {
return DVM.feedback(event, 'error', `Forbidden user: ${user}`);
}
const [label] = await eventsDB.query([{
const [label] = await Storages.db.query([{
kinds: [1985],
authors: [admin],
'#L': ['nip05'],

View File

@ -1,6 +1,6 @@
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
import { Conf } from '@/config.ts';
import { eventsDB, optimizer } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { Debug } from '@/deps.ts';
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
import { type DittoRelation } from '@/interfaces/DittoFilter.ts';
@ -31,8 +31,8 @@ const getEvent = async (
filter.kinds = [kind];
}
return await optimizer.query([filter], { limit: 1, signal })
.then((events) => hydrateEvents({ events, storage: optimizer, signal }))
return await Storages.optimizer.query([filter], { limit: 1, signal })
.then((events) => hydrateEvents({ events, storage: Storages.optimizer, signal }))
.then(([event]) => event);
};
@ -40,14 +40,14 @@ const getEvent = async (
const getAuthor = async (pubkey: string, opts: GetEventOpts = {}): Promise<NostrEvent | undefined> => {
const { signal = AbortSignal.timeout(1000) } = opts;
return await optimizer.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal })
.then((events) => hydrateEvents({ events, storage: optimizer, signal }))
return await Storages.optimizer.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal })
.then((events) => hydrateEvents({ events, storage: Storages.optimizer, signal }))
.then(([event]) => event);
};
/** Get users the given pubkey follows. */
const getFollows = async (pubkey: string, signal?: AbortSignal): Promise<NostrEvent | undefined> => {
const [event] = await eventsDB.query([{ authors: [pubkey], kinds: [3], limit: 1 }], { limit: 1, signal });
const [event] = await Storages.db.query([{ authors: [pubkey], kinds: [3], limit: 1 }], { limit: 1, signal });
return event;
};
@ -83,15 +83,15 @@ async function getAncestors(event: NostrEvent, result: NostrEvent[] = []): Promi
}
async function getDescendants(eventId: string, signal = AbortSignal.timeout(2000)): Promise<NostrEvent[]> {
const events = await eventsDB.query([{ kinds: [1], '#e': [eventId] }], { limit: 200, signal });
return hydrateEvents({ events, storage: eventsDB, signal });
const events = await Storages.db.query([{ kinds: [1], '#e': [eventId] }], { limit: 200, signal });
return hydrateEvents({ events, storage: Storages.db, signal });
}
/** Returns whether the pubkey is followed by a local user. */
async function isLocallyFollowed(pubkey: string): Promise<boolean> {
const { host } = Conf.url;
const [event] = await eventsDB.query(
const [event] = await Storages.db.query(
[{ kinds: [3], '#p': [pubkey], search: `domain:${host}`, limit: 1 }],
{ limit: 1 },
);

View File

@ -4,7 +4,7 @@ import { InsertQueryBuilder } from 'kysely';
import { db } from '@/db.ts';
import { DittoTables } from '@/db/DittoTables.ts';
import { Debug } from '@/deps.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { findReplyTag } from '@/tags.ts';
type AuthorStat = keyof Omit<DittoTables['author_stats'], 'pubkey'>;
@ -65,7 +65,7 @@ async function getStatsDiff(event: NostrEvent, prev: NostrEvent | undefined): Pr
case 5: {
if (!firstTaggedId) break;
const [repostedEvent] = await eventsDB.query(
const [repostedEvent] = await Storages.db.query(
[{ kinds: [6], ids: [firstTaggedId], authors: [event.pubkey] }],
{ limit: 1 },
);
@ -77,7 +77,7 @@ async function getStatsDiff(event: NostrEvent, prev: NostrEvent | undefined): Pr
const eventBeingRepostedPubkey = repostedEvent.tags.find(([name]) => name === 'p')?.[1];
if (!eventBeingRepostedId || !eventBeingRepostedPubkey) break;
const [eventBeingReposted] = await eventsDB.query(
const [eventBeingReposted] = await Storages.db.query(
[{ kinds: [1], ids: [eventBeingRepostedId], authors: [eventBeingRepostedPubkey] }],
{ limit: 1 },
);
@ -154,7 +154,7 @@ function eventStatsQuery(diffs: EventStatDiff[]) {
/** Get the last version of the event, if any. */
async function maybeGetPrev(event: NostrEvent): Promise<NostrEvent> {
const [prev] = await eventsDB.query([
const [prev] = await Storages.db.query([
{ kinds: [event.kind], authors: [event.pubkey], limit: 1 },
]);

View File

@ -9,51 +9,95 @@ import { PoolStore } from '@/storages/pool-store.ts';
import { Reqmeister } from '@/storages/reqmeister.ts';
import { SearchStore } from '@/storages/search-store.ts';
import { InternalRelay } from '@/storages/InternalRelay.ts';
import { UserStore } from '@/storages/UserStore.ts';
import { Time } from '@/utils/time.ts';
/** Relay pool storage. */
const client = new PoolStore({
pool,
relays: activeRelays,
publisher: pipeline,
});
/** SQLite database to store events this Ditto server cares about. */
const eventsDB = new EventsDB(db);
/** In-memory data store for cached events. */
const cache = new NCache({ max: 3000 });
/** Batches requests for single events. */
const reqmeister = new Reqmeister({
client,
delay: Time.seconds(1),
timeout: Time.seconds(1),
});
/** Main Ditto storage adapter */
const optimizer = new Optimizer({
db: eventsDB,
cache,
client: reqmeister,
});
/** Storage to use for remote search. */
const searchStore = new SearchStore({
relay: Conf.searchRelay,
fallback: optimizer,
});
export class Storages {
private static _db: EventsDB | undefined;
private static _admin: UserStore | undefined;
private static _cache: NCache | undefined;
private static _client: PoolStore | undefined;
private static _optimizer: Optimizer | undefined;
private static _reqmeister: Reqmeister | undefined;
private static _pubsub: InternalRelay | undefined;
private static _search: SearchStore | undefined;
static get pubsub(): InternalRelay {
/** SQLite database to store events this Ditto server cares about. */
public static get db(): EventsDB {
if (!this._db) {
this._db = new EventsDB(db);
}
return this._db;
}
/** Admin user storage. */
public static get admin(): UserStore {
if (!this._admin) {
this._admin = new UserStore(Conf.pubkey, this.db);
}
return this._admin;
}
/** Internal pubsub relay between controllers and the pipeline. */
public static get pubsub(): InternalRelay {
if (!this._pubsub) {
this._pubsub = new InternalRelay();
}
return this._pubsub;
}
}
export { cache, client, eventsDB, optimizer, reqmeister, searchStore };
/** Relay pool storage. */
public static get client(): PoolStore {
if (!this._client) {
this._client = new PoolStore({
pool,
relays: activeRelays,
publisher: pipeline,
});
}
return this._client;
}
/** In-memory data store for cached events. */
public static get cache(): NCache {
if (!this._cache) {
this._cache = new NCache({ max: 3000 });
}
return this._cache;
}
/** Batches requests for single events. */
public static get reqmeister(): Reqmeister {
if (!this._reqmeister) {
this._reqmeister = new Reqmeister({
client: this.client,
delay: Time.seconds(1),
timeout: Time.seconds(1),
});
}
return this._reqmeister;
}
/** Main Ditto storage adapter */
public static get optimizer(): Optimizer {
if (!this._optimizer) {
this._optimizer = new Optimizer({
db: this.db,
cache: this.cache,
client: this.reqmeister,
});
}
return this._optimizer;
}
/** Storage to use for remote search. */
public static get search(): SearchStore {
if (!this._search) {
this._search = new SearchStore({
relay: Conf.searchRelay,
fallback: this.optimizer,
});
}
return this._search;
}
}

View File

@ -1,7 +0,0 @@
import { UserStore } from '@/storages/UserStore.ts';
import { Conf } from '@/config.ts';
import { eventsDB } from '@/storages.ts';
export function getAdminStore() {
return new UserStore(Conf.pubkey, eventsDB);
}

View File

@ -9,7 +9,7 @@ import { Debug, parseFormData, type TypeFest } from '@/deps.ts';
import * as pipeline from '@/pipeline.ts';
import { AdminSigner } from '@/signers/AdminSigner.ts';
import { APISigner } from '@/signers/APISigner.ts';
import { client, eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { nostrNow } from '@/utils.ts';
const debug = Debug('ditto:api');
@ -43,7 +43,7 @@ async function updateEvent<E extends EventStub>(
fn: (prev: NostrEvent | undefined) => E,
c: AppContext,
): Promise<NostrEvent> {
const [prev] = await eventsDB.query([filter], { limit: 1, signal: c.req.raw.signal });
const [prev] = await Storages.db.query([filter], { limit: 1, signal: c.req.raw.signal });
return createEvent(fn(prev), c);
}
@ -80,7 +80,7 @@ async function publishEvent(event: NostrEvent, c: AppContext): Promise<NostrEven
try {
await Promise.all([
pipeline.handleEvent(event, c.req.raw.signal),
client.event(event),
Storages.client.event(event),
]);
} catch (e) {
if (e instanceof pipeline.RelayError) {

View File

@ -5,7 +5,7 @@ import { Conf } from '@/config.ts';
import { Debug } from '@/deps.ts';
import { SimpleLRU } from '@/utils/SimpleLRU.ts';
import { Time } from '@/utils/time.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { fetchWorker } from '@/workers/fetch.ts';
const debug = Debug('ditto:nip05');
@ -37,7 +37,7 @@ const nip05Cache = new SimpleLRU<string, nip19.ProfilePointer>(
);
async function localNip05Lookup(name: string): Promise<nip19.ProfilePointer | undefined> {
const [label] = await eventsDB.query([{
const [label] = await Storages.db.query([{
kinds: [1985],
authors: [Conf.pubkey],
'#L': ['nip05'],

View File

@ -1,10 +1,10 @@
import { Conf } from '@/config.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
export async function getRelays(pubkey: string): Promise<Set<string>> {
const relays = new Set<`wss://${string}`>();
const events = await eventsDB.query([
const events = await Storages.db.query([
{ kinds: [10002], authors: [pubkey, Conf.pubkey], limit: 2 },
]);

View File

@ -1,6 +1,6 @@
import { NostrFilter } from '@nostrify/nostrify';
import { AppContext } from '@/app.ts';
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { renderAccount } from '@/views/mastodon/accounts.ts';
import { renderStatus } from '@/views/mastodon/statuses.ts';
import { paginated, paginationSchema } from '@/utils/api.ts';
@ -12,15 +12,15 @@ async function renderEventAccounts(c: AppContext, filters: NostrFilter[], signal
return c.json([]);
}
const events = await eventsDB.query(filters, { signal });
const events = await Storages.db.query(filters, { signal });
const pubkeys = new Set(events.map(({ pubkey }) => pubkey));
if (!pubkeys.size) {
return c.json([]);
}
const authors = await eventsDB.query([{ kinds: [0], authors: [...pubkeys] }], { signal })
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
const authors = await Storages.db.query([{ kinds: [0], authors: [...pubkeys] }], { signal })
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
const accounts = await Promise.all(
authors.map((event) => renderAccount(event)),
@ -32,8 +32,8 @@ async function renderEventAccounts(c: AppContext, filters: NostrFilter[], signal
async function renderAccounts(c: AppContext, authors: string[], signal = AbortSignal.timeout(1000)) {
const { since, until, limit } = paginationSchema.parse(c.req.query());
const events = await eventsDB.query([{ kinds: [0], authors, since, until, limit }], { signal })
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
const events = await Storages.db.query([{ kinds: [0], authors, since, until, limit }], { signal })
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
const accounts = await Promise.all(
events.map((event) => renderAccount(event)),
@ -50,8 +50,8 @@ async function renderStatuses(c: AppContext, ids: string[], signal = AbortSignal
const { limit } = paginationSchema.parse(c.req.query());
const events = await eventsDB.query([{ kinds: [1], ids, limit }], { signal })
.then((events) => hydrateEvents({ events, storage: eventsDB, signal }));
const events = await Storages.db.query([{ kinds: [1], ids, limit }], { signal })
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
if (!events.length) {
return c.json([]);

View File

@ -1,8 +1,8 @@
import { eventsDB } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { hasTag } from '@/tags.ts';
async function renderRelationship(sourcePubkey: string, targetPubkey: string) {
const events = await eventsDB.query([
const events = await Storages.db.query([
{ kinds: [3], authors: [sourcePubkey], limit: 1 },
{ kinds: [3], authors: [targetPubkey], limit: 1 },
{ kinds: [10000], authors: [sourcePubkey], limit: 1 },

View File

@ -6,7 +6,7 @@ import { Conf } from '@/config.ts';
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
import { getMediaLinks, parseNoteContent } from '@/note.ts';
import { jsonMediaDataSchema } from '@/schemas/nostr.ts';
import { eventsDB, optimizer } from '@/storages.ts';
import { Storages } from '@/storages.ts';
import { findReplyTag } from '@/tags.ts';
import { nostrDate } from '@/utils.ts';
import { unfurlCardCached } from '@/utils/unfurl.ts';
@ -40,7 +40,7 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
),
];
const mentionedProfiles = await optimizer.query(
const mentionedProfiles = await Storages.optimizer.query(
[{ authors: mentionedPubkeys, limit: mentionedPubkeys.length }],
);
@ -53,7 +53,7 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
),
firstUrl ? unfurlCardCached(firstUrl) : null,
viewerPubkey
? await eventsDB.query([
? await Storages.db.query([
{ kinds: [6], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
{ kinds: [7], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
{ kinds: [9734], '#e': [event.id], authors: [viewerPubkey], limit: 1 },