feat(session): add per-user source refresh (#84)

* feat(session): add per-user source refresh

Add refreshSource(provider) to UserSession so per-user
config changes can re-resolve a source without replacing
the global provider.

- UserSession now carries userId
- Simplify UserSessionManager sessions map
- replaceProvider delegates to session.refreshSource
- Remove updateSessionSource from manager

Co-authored-by: Ona <no-reply@ona.com>

* docs: fix stale jsdoc on provider failure behavior

Co-authored-by: Ona <no-reply@ona.com>

---------

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2026-03-22 00:13:22 +00:00
committed by GitHub
parent 7862a6d367
commit b24d879d31
4 changed files with 137 additions and 52 deletions

View File

@@ -1,8 +1,10 @@
import { FeedEngine, type FeedItem, type FeedResult, type FeedSource } from "@aelis/core"
import type { FeedEnhancer } from "../enhancement/enhance-feed.ts"
import type { FeedSourceProvider } from "./feed-source-provider.ts"
export class UserSession {
readonly userId: string
readonly engine: FeedEngine
private sources = new Map<string, FeedSource>()
private readonly enhancer: FeedEnhancer | null
@@ -12,7 +14,8 @@ export class UserSession {
private enhancingPromise: Promise<void> | null = null
private unsubscribe: (() => void) | null = null
constructor(sources: FeedSource[], enhancer?: FeedEnhancer | null) {
constructor(userId: string, sources: FeedSource[], enhancer?: FeedEnhancer | null) {
this.userId = userId
this.engine = new FeedEngine()
this.enhancer = enhancer ?? null
for (const source of sources) {
@@ -67,6 +70,27 @@ export class UserSession {
return this.sources.get(sourceId) as T | undefined
}
/**
* Re-resolves a source from its provider using this session's userId.
* The source must already be registered. Throws if it isn't.
* If the provider fails, the existing source is kept.
*/
async refreshSource(provider: FeedSourceProvider): Promise<void> {
if (!this.sources.has(provider.sourceId)) {
throw new Error(`Cannot refresh source "${provider.sourceId}": not registered`)
}
try {
const newSource = await provider.feedSourceForUser(this.userId)
this.replaceSource(provider.sourceId, newSource)
} catch (err) {
console.error(
`[UserSession] refreshSource("${provider.sourceId}") failed for user ${this.userId}:`,
err,
)
}
}
/**
* Replaces a source in the engine and invalidates all caches.
* Stops and restarts the engine to re-establish reactive subscriptions.