perf(server): parallel media-server inventory, queued identify remote lookups, dev DB cleanup
- GET /inventory/media-servers: per-instance Promise.allSettled, per-instance
concurrency cap (2) and timeout (8s); failed/timed-out instances are unknown,
response carries partial=true (field not yet in contracts)
- S-7: POST /files/identify returns local matches plus remoteTaskId/remotePending
and enqueues identifyRemote; the worker runs the TMDB lookups and publishes SSE
identify.completed {taskId,status,items,truncated,excludedBySize,error};
?wait=true keeps the synchronous path, capped at 15s overall
- catalog.lookup / release.search honour the caller's abort signal
- scripts/dev-db-clean.ts: dry-run by default, --yes to drop leftover test
schemas and w32-/w33-/fe4-/qa- prefixed entities; refuses non localhost:55433
- runbook: dev DB cleanup
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5b8927d1dc
commit
fd2f7bc33d
10 files changed
+683
-67
No files matched your search
@@ -16,7 +16,7 @@ import { liveSearch } from "./service/workbench.ts";
|
||||
|
||||
export function registerSourcesCapabilities(registry: CapabilityRegistry, rt: () => SourcesRuntime): void {
|
||||
registerSiteCapabilities(registry, rt);
|
||||
registry.register(capabilitySpec("catalog.lookup"), async (input) => {
|
||||
registry.register(capabilitySpec("catalog.lookup"), async (input, ctx) => {
|
||||
const { db } = rt().ctx;
|
||||
const local: { entity: unknown; score: number; matchedAlias: string | null }[] = [];
|
||||
if (input.externalId) {
|
||||
@@ -57,10 +57,9 @@ export function registerSourcesCapabilities(registry: CapabilityRegistry, rt: ()
|
||||
}[] = [];
|
||||
const tmdb = await rt().tmdb();
|
||||
if (input.includeRemote && input.query && tmdb) {
|
||||
for (const m of (await tmdb.searchAll(input.query, input.year ? { year: input.year } : {})).slice(
|
||||
0,
|
||||
input.limit,
|
||||
)) {
|
||||
// 认调用方的 signal(HTTP 15s 超时 / 任务取消):中止后立即返回,不再等 TMDB(底层请求由客户端自身超时收尾)
|
||||
const found = await abortable(tmdb.searchAll(input.query, input.year ? { year: input.year } : {}), ctx.signal);
|
||||
for (const m of found.slice(0, input.limit)) {
|
||||
remote.push({
|
||||
provider: "tmdb",
|
||||
entityType: m.entityType,
|
||||
@@ -73,7 +72,7 @@ export function registerSourcesCapabilities(registry: CapabilityRegistry, rt: ()
|
||||
return { local: local as never, remote };
|
||||
});
|
||||
|
||||
registry.register(capabilitySpec("release.search"), async (input) => {
|
||||
registry.register(capabilitySpec("release.search"), async (input, ctx) => {
|
||||
const r = rt();
|
||||
const { db } = r.ctx;
|
||||
let q = db.selectFrom("source_release").selectAll();
|
||||
@@ -93,7 +92,10 @@ export function registerSourcesCapabilities(registry: CapabilityRegistry, rt: ()
|
||||
const coverage: { siteId: string; fromSnapshot: boolean; live: boolean; error: string | null }[] = [];
|
||||
const needLive = input.allowLiveSearch && !!text && rows.length === 0;
|
||||
for (const s of sites) {
|
||||
if (needLive) {
|
||||
if (needLive && ctx.signal.aborted) {
|
||||
// 已超时/取消:剩余站点不再实时补查
|
||||
coverage.push({ siteId: s.id, fromSnapshot: true, live: false, error: "aborted" });
|
||||
} else if (needLive) {
|
||||
const res = await liveSearch(r, s, text!);
|
||||
coverage.push({ siteId: s.id, fromSnapshot: false, live: res.error === null, error: res.error });
|
||||
} else coverage.push({ siteId: s.id, fromSnapshot: true, live: false, error: null });
|
||||
@@ -188,3 +190,15 @@ async function identities(r: SourcesRuntime, entityId: string) {
|
||||
.execute();
|
||||
return toApiList(rows) as never[];
|
||||
}
|
||||
|
||||
/** 调用方 signal 中止时立即 reject(原 Promise 结果丢弃) */
|
||||
function abortable<T>(p: Promise<T>, signal: AbortSignal | undefined): Promise<T> {
|
||||
if (!signal) return p;
|
||||
if (signal.aborted) return Promise.reject(signal.reason);
|
||||
p.catch(() => {});
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const onAbort = () => reject(signal.reason);
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
p.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user