···11import { pino } from 'pino'
22import { Database } from '../db/connection.js'
33-import { BidirectionalResolver } from '../id-resolver.js'
33+import type { BidirectionalResolver } from '../utils/id-resolver.js'
44+import { customConfig } from '../utils/logger-config.js'
4555-const logger = pino({ name: 'indexing-service' })
66+const logger = pino(customConfig('indexing-service'))
6778/**
89 * Service to handle indexing of actors and their handles
910 */
1011export class IndexingService {
1111- private logger = pino({ name: 'indexing-service' })
1212+ private logger = pino(customConfig('indexing-service'))
12131314 constructor(
1415 private db: Database,
···17181819 /**
1920 * Index or update actor handle information
2020- *
2121+ *
2122 * @param did The DID of the actor
2223 * @param timestamp The timestamp of the operation
2324 * @param force Force reindexing even if recently indexed
2425 */
2525-2626+2627 async indexHandle(did: string, timestamp: string, force = false): Promise<void> {
2728 try {
2829 // Find existing actor
2930 const actor = await this.db.models.Actor.findOne({ did })
3030-3131+3132 // Skip if recently indexed and not forced
3233 if (!force && actor && this.isHandleRecentlyIndexed(actor, timestamp)) {
3334 return
···35363637 // Resolve DID to handle
3738 const didDoc = await this.resolver.resolveDidToDidDoc(did)
3838-3939+3940 // Verify handle ownership
4041 let handle: string | undefined = undefined
4142 if (didDoc.handle) {
···5859 // Update or create actor
5960 await this.db.models.Actor.updateOne(
6061 { did },
6161- {
6262- $set: {
6262+ {
6363+ $set: {
6364 handle,
6465 indexedAt: timestamp
6566 },
···7677 */
7778 private isHandleRecentlyIndexed(actor: any, timestamp: string): boolean {
7879 if (!actor.indexedAt) return false
7979-8080+8081 const timeDiff = new Date(timestamp).getTime() - new Date(actor.indexedAt).getTime()
8182 const ONE_DAY = 24 * 60 * 60 * 1000
8283 const ONE_HOUR = 60 * 60 * 1000
8383-8484+8485 // Reindex daily for all actors
8586 if (timeDiff > ONE_DAY) return false
8686-8787+8788 // Reindex more frequently for actors without handles
8889 if (actor.handle === null && timeDiff > ONE_HOUR) return false
8989-9090+9091 return true
9192 }
9292-} 9393+}
+72
services/ingester/src/utils/actor-cache.ts
···11+import { pino } from 'pino'
22+import { customConfig } from './logger-config.js'
33+import { TtlCache } from './ttl-cache.js'
44+import { Database } from '../db/connection.js'
55+66+const logger = pino(customConfig('actor-cache'))
77+const actorsCache = new TtlCache<string, Set<string>>({ defaultTtlMs: 60_000 }) // 60 seconds TTL
88+const ACTORS_CACHE_KEY = 'all_actor_dids'
99+1010+// Cache refresh lock to prevent multiple simultaneous refreshes
1111+let cacheRefreshInProgress = false
1212+let refreshPromise: Promise<Set<string>> | null = null
1313+1414+/**
1515+ * Queries the database directly to check if an actor with the given DID exists
1616+ */
1717+async function queryActorDirectly(did: string, db: Database): Promise<boolean> {
1818+ return !!(await db.models.Actor.findOne({ did }).lean())
1919+}
2020+2121+/**
2222+ * Checks if an actor with the given DID exists in the database using cache for optimization
2323+ */
2424+export async function isActorInDatabase(did: string, db: Database): Promise<boolean> {
2525+ // Check if actors are already cached
2626+ const actorDids = actorsCache.get(ACTORS_CACHE_KEY)
2727+ if (actorDids) {
2828+ return actorDids.has(did)
2929+ }
3030+3131+ // If a refresh is already in progress, wait for it
3232+ if (cacheRefreshInProgress && refreshPromise) {
3333+ try {
3434+ const refreshedActors = await refreshPromise
3535+ return refreshedActors.has(did)
3636+ } catch (error) {
3737+ logger.warn({ error, did }, 'Shared cache refresh failed, falling back to direct query')
3838+ return queryActorDirectly(did, db)
3939+ }
4040+ }
4141+4242+ // Start a new cache refresh
4343+ cacheRefreshInProgress = true
4444+ refreshPromise = refreshActorsCache(db)
4545+4646+ try {
4747+ const refreshedActors = await refreshPromise
4848+ return refreshedActors.has(did)
4949+ } catch (error) {
5050+ logger.warn({ error, did }, 'Cache refresh failed, falling back to direct query')
5151+ return queryActorDirectly(did, db)
5252+ } finally {
5353+ cacheRefreshInProgress = false
5454+ refreshPromise = null
5555+ }
5656+}
5757+5858+/**
5959+ * Refreshes the actors cache by fetching all actors from the database
6060+ */
6161+async function refreshActorsCache(db: Database): Promise<Set<string>> {
6262+ try {
6363+ const actors = await db.models.Actor.find({}, { did: 1, _id: 0 }).lean()
6464+ const actorDids = new Set(actors.map((actor) => actor.did))
6565+ actorsCache.set(ACTORS_CACHE_KEY, actorDids)
6666+ logger.info({ actorCount: actorDids.size }, 'Refreshed actors cache')
6767+ return actorDids
6868+ } catch (error) {
6969+ logger.error({ error }, 'Failed to fetch actors for caching')
7070+ throw error
7171+ }
7272+}
+11-10
services/ingester/src/utils/actor-utils.ts
···11import { pino } from 'pino'
22import { Database } from '../db/connection.js'
33+import { customConfig } from './logger-config.js'
3444-const logger = pino({ name: 'actor-utils' })
55+const logger = pino(customConfig('actor-utils'))
5667/**
78 * Ensures that an actor exists for the given DID.
89 * If the actor doesn't exist, it creates a new one.
99- *
1010+ *
1011 * @param did The DID to ensure has an actor
1112 * @param handle Optional handle associated with the DID
1213 * @param db Database connection
1314 * @returns The actor document, either existing or newly created
1415 */
1516export async function ensureActor(
1616- did: string,
1717- handle?: string,
1717+ did: string,
1818+ handle?: string,
1819 db?: Database
1920): Promise<any> {
2021 if (!db) {
···2526 try {
2627 // Try to find existing actor
2728 const existingActor = await db.models.Actor.findOne({ did })
2828-2929+2930 if (existingActor) {
3031 // If handle is provided and different from existing, update it
3132 if (handle && existingActor.handle !== handle) {
···3940 // Create new actor if none exists
4041 const now = new Date()
4142 const uri = `at://${did}/so.sprk.actor.profile`
4242-4343+4344 const newActor = await db.models.Actor.create({
4445 uri,
4546 did,
···62636364/**
6465 * Links a profile to an actor
6565- *
6666+ *
6667 * @param did The DID of the actor
6768 * @param profileId The MongoDB ID of the profile
6869 * @param profileCid The CID of the profile
···7778 try {
7879 await db.models.Actor.findOneAndUpdate(
7980 { did },
8080- {
8181+ {
8182 profile: profileId,
8283 profileCid
8384 },
8485 { new: true }
8586 )
8686-8787+8788 logger.info({ did, profileId }, 'Linked profile to actor')
8889 } catch (error) {
8990 logger.error({ error, did, profileId }, 'Failed to link profile to actor')
9091 }
9191-} 9292+}