[READ ONLY MIRROR] Spark Social AppView Server github.com/sprksocial/server
atproto deno hono lexicon
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 126 lines 3.0 kB view raw
1import { l, lexParse } from "@atp/lex"; 2import { AtUri } from "@atp/syntax"; 3import { Record } from "../data-plane/routes/records.ts"; 4 5export class HydrationMap<T> extends Map<string, T | null> implements Merges { 6 merge(map: HydrationMap<T>): this { 7 map.forEach((val, key) => { 8 this.set(key, val); 9 }); 10 return this; 11 } 12} 13 14export interface Merges { 15 merge<T extends this>(map: T): this; 16} 17 18type UnknownRecord = { $type: string; [x: string]: unknown }; 19 20export type RecordInfo<T extends UnknownRecord> = { 21 record: T; 22 cid: string; 23 sortedAt: Date; 24 indexedAt: Date; 25 takedownRef: string | undefined; 26}; 27 28export const mergeMaps = <V, M extends HydrationMap<V>>( 29 mapA?: M, 30 mapB?: M, 31): M | undefined => { 32 if (!mapA) return mapB; 33 if (!mapB) return mapA; 34 return mapA.merge(mapB); 35}; 36 37export const mergeNestedMaps = <V, M extends HydrationMap<HydrationMap<V>>>( 38 mapA?: M, 39 mapB?: M, 40): M | undefined => { 41 if (!mapA) return mapB; 42 if (!mapB) return mapA; 43 44 for (const [key, map] of mapB) { 45 const merged = mergeMaps(mapA.get(key) ?? undefined, map ?? undefined); 46 mapA.set(key, merged ?? null); 47 } 48 49 return mapA; 50}; 51 52export const mergeManyMaps = <T>(...maps: HydrationMap<T>[]) => { 53 return maps.reduce(mergeMaps, undefined as HydrationMap<T> | undefined); 54}; 55 56export type ItemRef = { uri: string; cid?: string }; 57 58export const parseRecord = <T extends UnknownRecord>( 59 recordSchema: l.RecordSchema, 60 entry: Record, 61 includeTakedowns: boolean, 62): RecordInfo<T> | undefined => { 63 if (!includeTakedowns && entry.takenDown) { 64 return undefined; 65 } 66 let record: unknown; 67 try { 68 record = lexParse(entry.record, { strict: false }); 69 } catch { 70 return; 71 } 72 const cid = entry.cid; 73 const sortedAt = new Date(entry.sortedAt ?? 0); 74 const indexedAt = new Date(entry.indexedAt ?? 0); 75 if (!record || !cid) return; 76 if (!recordSchema.$matches(record)) { 77 return; 78 } 79 return { 80 record: record as T, 81 cid, 82 sortedAt, 83 indexedAt, 84 takedownRef: safeTakedownRef(entry), 85 }; 86}; 87 88export const parseString = (str: string | undefined): string | undefined => { 89 return str && str.length > 0 ? str : undefined; 90}; 91 92export const urisByCollection = (uris: string[]): Map<string, string[]> => { 93 const result = new Map<string, string[]>(); 94 for (const uri of uris) { 95 const collection = new AtUri(uri).collection; 96 const items = result.get(collection) ?? []; 97 items.push(uri); 98 result.set(collection, items); 99 } 100 return result; 101}; 102 103export const split = <T>( 104 items: T[], 105 predicate: (item: T) => boolean, 106): [T[], T[]] => { 107 const yes: T[] = []; 108 const no: T[] = []; 109 for (const item of items) { 110 if (predicate(item)) { 111 yes.push(item); 112 } else { 113 no.push(item); 114 } 115 } 116 return [yes, no]; 117}; 118 119export const safeTakedownRef = (obj?: { 120 takenDown: boolean; 121 takedownRef?: string | undefined; 122}): string | undefined => { 123 if (!obj) return; 124 if (obj.takedownRef) return obj.takedownRef; 125 if (obj.takenDown) return "SPRK-TAKEDOWN-UNKNOWN"; 126};