refactor: isolate playback and upload use cases
ci / module boundaries / boundaries (push) Successful in 6s
ci / api / api (push) Successful in 47s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / db / db (push) Successful in 1m4s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m30s
ci / ts / ts (packages + web) (push) Successful in 2m54s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m14s
ci / module boundaries / boundaries (push) Successful in 6s
ci / api / api (push) Successful in 47s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / db / db (push) Successful in 1m4s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m30s
ci / ts / ts (packages + web) (push) Successful in 2m54s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m14s
This commit is contained in:
1 parent
c84f90e2cc
commit
e158558da3
56 files changed
+3337
-2196
No files matched your search
@@ -0,0 +1,62 @@
|
||||
/** Policy shared by web and native players; queue ownership stays with the adapter. */
|
||||
export const EARLY_SKIP_MS = 30_000;
|
||||
export const WAVE_REFILL_AT = 2;
|
||||
export type WaveOutcome = 'complete' | 'skip' | 'skip_early' | 'like' | 'dislike';
|
||||
export interface WaveInfo {
|
||||
sessionId: string;
|
||||
activity: string | null;
|
||||
mood: string | null;
|
||||
fallback: boolean;
|
||||
}
|
||||
export interface WaveFeedback { track_id: string; outcome: WaveOutcome }
|
||||
export interface WaveGateway<T> {
|
||||
next(sessionId: string, feedback: WaveFeedback[]): Promise<T>;
|
||||
}
|
||||
|
||||
export class WaveCoordinator<T> {
|
||||
private sessionId: string | null = null;
|
||||
private generation = 0;
|
||||
private pending: WaveFeedback[] = [];
|
||||
private loading: number | null = null;
|
||||
|
||||
constructor(private readonly gateway: WaveGateway<T>) {}
|
||||
|
||||
get version() { return this.generation; }
|
||||
isCurrent(version: number) { return version === this.generation; }
|
||||
|
||||
/** Also invalidates in-flight responses when the context changes within a session. */
|
||||
activate(sessionId: string | null) {
|
||||
this.sessionId = sessionId;
|
||||
this.pending = [];
|
||||
this.loading = null;
|
||||
return ++this.generation;
|
||||
}
|
||||
|
||||
changeContext(sessionId: string) {
|
||||
const pending = this.sessionId === sessionId ? this.pending : [];
|
||||
const version = this.activate(sessionId);
|
||||
this.pending = pending;
|
||||
return version;
|
||||
}
|
||||
|
||||
note(trackId: string, outcome: WaveOutcome) {
|
||||
if (this.sessionId) this.pending.push({ track_id: trackId, outcome });
|
||||
}
|
||||
|
||||
async refill(remaining: number, refresh: boolean, apply: (result: T, current: () => boolean) => void | Promise<void>) {
|
||||
const id = this.sessionId;
|
||||
const version = this.generation;
|
||||
if (!id || this.loading === version || (!refresh && remaining > WAVE_REFILL_AT)) return;
|
||||
this.loading = version;
|
||||
const feedback = this.pending;
|
||||
this.pending = [];
|
||||
try {
|
||||
const result = await this.gateway.next(id, feedback);
|
||||
if (this.isCurrent(version)) await apply(result, () => this.isCurrent(version));
|
||||
} catch {
|
||||
if (this.isCurrent(version)) this.pending = [...feedback, ...this.pending];
|
||||
} finally {
|
||||
if (this.loading === version) this.loading = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user