@media-engine/core 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +40 -255
  2. package/README.ru.md +80 -0
  3. package/dist/cache/memory.d.ts +5 -0
  4. package/dist/cache/memory.js +43 -3
  5. package/dist/cache/memory.js.map +1 -1
  6. package/dist/cache/types.d.ts +2 -0
  7. package/dist/details/types.d.ts +6 -0
  8. package/dist/engine/availability.d.ts +6 -0
  9. package/dist/engine/availability.js +137 -0
  10. package/dist/engine/availability.js.map +1 -0
  11. package/dist/engine/circuit-breaker.d.ts +37 -0
  12. package/dist/engine/circuit-breaker.js +174 -0
  13. package/dist/engine/circuit-breaker.js.map +1 -0
  14. package/dist/engine/concurrency-limiter.d.ts +13 -0
  15. package/dist/engine/concurrency-limiter.js +109 -0
  16. package/dist/engine/concurrency-limiter.js.map +1 -0
  17. package/dist/engine/engine.d.ts +15 -5
  18. package/dist/engine/engine.js +510 -848
  19. package/dist/engine/engine.js.map +1 -1
  20. package/dist/engine/in-flight.d.ts +15 -0
  21. package/dist/engine/in-flight.js +80 -0
  22. package/dist/engine/in-flight.js.map +1 -0
  23. package/dist/engine/operation.d.ts +7 -0
  24. package/dist/engine/operation.js +49 -0
  25. package/dist/engine/operation.js.map +1 -0
  26. package/dist/engine/provider-calls.d.ts +52 -0
  27. package/dist/engine/provider-calls.js +262 -0
  28. package/dist/engine/provider-calls.js.map +1 -0
  29. package/dist/engine/query.d.ts +26 -0
  30. package/dist/engine/query.js +430 -0
  31. package/dist/engine/query.js.map +1 -0
  32. package/dist/engine/response-meta.d.ts +15 -0
  33. package/dist/engine/response-meta.js +33 -0
  34. package/dist/engine/response-meta.js.map +1 -0
  35. package/dist/engine/runtime.d.ts +5 -0
  36. package/dist/engine/runtime.js +48 -0
  37. package/dist/engine/runtime.js.map +1 -0
  38. package/dist/engine/search-discovery.d.ts +3 -0
  39. package/dist/engine/search-discovery.js +35 -0
  40. package/dist/engine/search-discovery.js.map +1 -0
  41. package/dist/engine/search-enrichment-shared.d.ts +23 -0
  42. package/dist/engine/search-enrichment-shared.js +68 -0
  43. package/dist/engine/search-enrichment-shared.js.map +1 -0
  44. package/dist/engine/search-enrichment.d.ts +42 -0
  45. package/dist/engine/search-enrichment.js +150 -0
  46. package/dist/engine/search-enrichment.js.map +1 -0
  47. package/dist/engine/search-identity-snapshot.d.ts +15 -0
  48. package/dist/engine/search-identity-snapshot.js +101 -0
  49. package/dist/engine/search-identity-snapshot.js.map +1 -0
  50. package/dist/engine/search-outcomes.d.ts +32 -0
  51. package/dist/engine/search-outcomes.js +118 -0
  52. package/dist/engine/search-outcomes.js.map +1 -0
  53. package/dist/engine/search-poster-enrichment.d.ts +31 -0
  54. package/dist/engine/search-poster-enrichment.js +93 -0
  55. package/dist/engine/search-poster-enrichment.js.map +1 -0
  56. package/dist/engine/search-result-freeze.d.ts +13 -0
  57. package/dist/engine/search-result-freeze.js +155 -0
  58. package/dist/engine/search-result-freeze.js.map +1 -0
  59. package/dist/engine/stale-fallback.d.ts +11 -0
  60. package/dist/engine/stale-fallback.js +60 -0
  61. package/dist/engine/stale-fallback.js.map +1 -0
  62. package/dist/engine/timeout-budget.d.ts +7 -0
  63. package/dist/engine/timeout-budget.js +31 -0
  64. package/dist/engine/timeout-budget.js.map +1 -0
  65. package/dist/engine/torrents.d.ts +22 -0
  66. package/dist/engine/torrents.js +188 -0
  67. package/dist/engine/torrents.js.map +1 -0
  68. package/dist/engine/types.d.ts +35 -0
  69. package/dist/errors/types.d.ts +1 -1
  70. package/dist/errors/types.js.map +1 -1
  71. package/dist/index.d.ts +2 -1
  72. package/dist/index.js +1 -1
  73. package/dist/index.js.map +1 -1
  74. package/dist/media/types.d.ts +1 -1
  75. package/dist/merge/details-identity.d.ts +5 -0
  76. package/dist/merge/details-identity.js +63 -0
  77. package/dist/merge/details-identity.js.map +1 -0
  78. package/dist/merge/diversity.d.ts +15 -0
  79. package/dist/merge/diversity.js +52 -0
  80. package/dist/merge/diversity.js.map +1 -0
  81. package/dist/merge/fields.d.ts +25 -0
  82. package/dist/merge/fields.js +364 -0
  83. package/dist/merge/fields.js.map +1 -0
  84. package/dist/merge/grouping.d.ts +3 -0
  85. package/dist/merge/grouping.js +143 -0
  86. package/dist/merge/grouping.js.map +1 -0
  87. package/dist/merge/identity.d.ts +5 -0
  88. package/dist/merge/identity.js +21 -0
  89. package/dist/merge/identity.js.map +1 -0
  90. package/dist/merge/internal.d.ts +22 -0
  91. package/dist/merge/internal.js +15 -0
  92. package/dist/merge/internal.js.map +1 -0
  93. package/dist/merge/media-type.d.ts +5 -0
  94. package/dist/merge/media-type.js +15 -0
  95. package/dist/merge/media-type.js.map +1 -0
  96. package/dist/merge/priority.d.ts +9 -0
  97. package/dist/merge/priority.js +63 -0
  98. package/dist/merge/priority.js.map +1 -0
  99. package/dist/merge/scoring.d.ts +15 -0
  100. package/dist/merge/scoring.js +364 -0
  101. package/dist/merge/scoring.js.map +1 -0
  102. package/dist/merge/strategy.js +66 -955
  103. package/dist/merge/strategy.js.map +1 -1
  104. package/dist/merge/title.d.ts +4 -0
  105. package/dist/merge/title.js +22 -0
  106. package/dist/merge/title.js.map +1 -0
  107. package/dist/providers/registry.d.ts +4 -2
  108. package/dist/providers/registry.js +11 -2
  109. package/dist/providers/registry.js.map +1 -1
  110. package/dist/providers/types.d.ts +4 -0
  111. package/dist/response/types.d.ts +21 -0
  112. package/dist/search/types.d.ts +34 -0
  113. package/dist/testing/providers.js +2 -0
  114. package/dist/testing/providers.js.map +1 -1
  115. package/dist/torrent/index.d.ts +1 -0
  116. package/dist/torrent/index.js +2 -0
  117. package/dist/torrent/index.js.map +1 -0
  118. package/dist/torrent/types.d.ts +132 -0
  119. package/dist/torrent/types.js +2 -0
  120. package/dist/torrent/types.js.map +1 -0
  121. package/package.json +13 -4
@@ -0,0 +1,93 @@
1
+ import { callTimedProviderDetails } from "./provider-calls.js";
2
+ import { createSearchDetailsQuery, hasSharedExternalId, searchItemAsDetails, supportsSearchEnrichment, supportsSearchEnrichmentFeature, } from "./search-enrichment-shared.js";
3
+ // Loads the best canonical poster while sharing matching ID-search work from the same plan.
4
+ // Загружает лучший canonical poster, переиспользуя совпадающий ID-search того же plan.
5
+ export async function loadSearchPoster(input) {
6
+ const query = createSearchDetailsQuery(input.result, input.language);
7
+ const selectedProviders = input.registry.selectDetailsProviders(query);
8
+ const providers = selectedProviders.filter((provider) => !input.excludedProviders.has(provider.name) &&
9
+ supportsSearchEnrichment(provider) &&
10
+ supportsSearchEnrichmentFeature(provider, "posters"));
11
+ if (input.reusableDetails?.poster) {
12
+ return {
13
+ ids: input.result.item.ids,
14
+ poster: input.reusableDetails.poster,
15
+ outcomes: [],
16
+ skipped: selectedProviders.length,
17
+ };
18
+ }
19
+ const searchProviders = new Set(input.result.sources.map((source) => source.provider));
20
+ const reusableSearchProviders = new Set(providers
21
+ .filter((provider) => provider.searchPosterMatchesDetails === true && searchProviders.has(provider.name))
22
+ .map((provider) => provider.name));
23
+ const reusablePosterProvider = providers.find((provider) => reusableSearchProviders.has(provider.name) &&
24
+ provider.name === input.result.item.poster?.source &&
25
+ input.result.item.poster);
26
+ const loads = await Promise.all(providers.map((provider) => loadPosterProviderResult(provider, query, input, reusableSearchProviders, reusablePosterProvider)));
27
+ const outcomes = loads.flatMap((load) => (load.outcome ? [load.outcome] : []));
28
+ const providerResults = loads.flatMap((load) => (load.result ? [load.result] : []));
29
+ return {
30
+ ids: input.result.item.ids,
31
+ poster: input.mergeStrategy.mergeDetails(providerResults, {
32
+ query,
33
+ language: input.language,
34
+ debug: input.debug,
35
+ warnings: [],
36
+ })?.poster,
37
+ outcomes,
38
+ skipped: selectedProviders.length - outcomes.length,
39
+ };
40
+ }
41
+ async function loadPosterProviderResult(provider, query, input, reusableSearchProviders, reusablePosterProvider) {
42
+ if (provider.name === reusablePosterProvider?.name) {
43
+ return {
44
+ result: {
45
+ provider: provider.name,
46
+ details: searchItemAsDetails(input.result.item),
47
+ },
48
+ };
49
+ }
50
+ if (reusableSearchProviders.has(provider.name)) {
51
+ return {};
52
+ }
53
+ if (provider.searchPosterMatchesDetails === true &&
54
+ provider.name === input.idEnrichment?.provider.name) {
55
+ const outcome = await input.idEnrichment.outcome;
56
+ const reusableResult = outcome.failure
57
+ ? undefined
58
+ : outcome.results.find((result) => result.item.poster && hasSharedExternalId(result.item.ids, query.ids));
59
+ if (reusableResult) {
60
+ return {
61
+ result: {
62
+ provider: provider.name,
63
+ details: searchItemAsDetails(reusableResult.item),
64
+ },
65
+ };
66
+ }
67
+ }
68
+ const timeoutMs = input.budget.reserve(provider.name);
69
+ if (timeoutMs === undefined) {
70
+ return {};
71
+ }
72
+ const outcome = await callTimedProviderDetails(provider, query, {
73
+ debug: input.debug,
74
+ language: input.language,
75
+ signal: input.signal,
76
+ circuitBreaker: input.circuitBreaker,
77
+ concurrencyLimiter: input.concurrencyLimiter,
78
+ timeoutMs,
79
+ });
80
+ return {
81
+ outcome,
82
+ result: outcome.failure || !outcome.result ? undefined : outcome.result,
83
+ };
84
+ }
85
+ // Applies canonical poster enrichments to matching search results.
86
+ // Применяет canonical poster enrichment к совпадающим search results.
87
+ export function applySearchPosterEnrichments(results, enrichments) {
88
+ return results.map((result) => {
89
+ const poster = enrichments.find((enrichment) => enrichment.poster && hasSharedExternalId(result.item.ids, enrichment.ids))?.poster;
90
+ return poster ? { ...result, item: { ...result.item, poster } } : result;
91
+ });
92
+ }
93
+ //# sourceMappingURL=search-poster-enrichment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-poster-enrichment.js","sourceRoot":"","sources":["../../src/engine/search-poster-enrichment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { ExternalIds } from "../media/index.js";
2
+ import type { EngineWarning } from "../response/index.js";
3
+ import type { MediaSearchResult, SearchQuery } from "../search/index.js";
4
+ import type { ProviderSearchCallOutcome } from "./provider-calls.js";
5
+ export interface SearchIdEnrichment {
6
+ ids: ExternalIds | undefined;
7
+ outcome: ProviderSearchCallOutcome;
8
+ }
9
+ export declare function applySearchIdEnrichments(results: MediaSearchResult[], enrichments: SearchIdEnrichment[], warnings: EngineWarning[], language: string | undefined): MediaSearchResult[];
10
+ export declare function finalizeSearchRankingEvidence(results: MediaSearchResult[]): MediaSearchResult[];
11
+ export declare function createFrozenDiscoveryResults(rankedResults: MediaSearchResult[], preliminaryResults: MediaSearchResult[]): MediaSearchResult[];
12
+ export declare function createSearchEnrichmentCandidates(frozenResults: MediaSearchResult[], preliminaryResults: MediaSearchResult[]): MediaSearchResult[];
13
+ export declare function filterFrozenSearchResults(results: MediaSearchResult[], query: SearchQuery): MediaSearchResult[];
@@ -0,0 +1,155 @@
1
+ import { mergeAlternativeTitles, mergeGenres, mergeRatings, selectBestImage, selectDescription, selectReleaseDate, selectShortDescription, } from "../merge/fields.js";
2
+ import { hasSharedStrongId } from "../merge/identity.js";
3
+ import { EXTERNAL_ID_KEYS } from "../merge/internal.js";
4
+ import { titleRelevanceScore } from "../merge/scoring.js";
5
+ import { hasSharedExternalId } from "./search-enrichment-shared.js";
6
+ // Applies optional provider data to frozen discovery candidates without changing identity/order.
7
+ // Применяет optional provider data к frozen discovery candidates без изменения identity/order.
8
+ export function applySearchIdEnrichments(results, enrichments, warnings, language) {
9
+ return results.map((result) => {
10
+ const candidates = enrichments.flatMap((enrichment) => {
11
+ if (enrichment.outcome.failure || !hasSharedExternalId(result.item.ids, enrichment.ids)) {
12
+ return [];
13
+ }
14
+ return enrichment.outcome.results.filter((candidate) => hasSharedExternalId(candidate.item.ids, enrichment.ids) &&
15
+ arePresentationTypesCompatible(result.item, candidate.item));
16
+ });
17
+ if (candidates.length === 0) {
18
+ return result;
19
+ }
20
+ const entries = createPresentationEntries(result, candidates);
21
+ const imageEntries = [...entries.slice(1), entries[0]];
22
+ return {
23
+ ...result,
24
+ item: {
25
+ ...result.item,
26
+ alternativeTitles: mergeAlternativeTitles(entries, result.item.title),
27
+ releaseDate: selectReleaseDate(entries),
28
+ description: selectDescription(entries, { language }),
29
+ shortDescription: selectShortDescription(entries),
30
+ poster: selectBestImage(imageEntries, "poster"),
31
+ backdrop: selectBestImage(imageEntries, "backdrop"),
32
+ genres: mergeGenres(entries),
33
+ ratings: mergeRatings(entries),
34
+ ids: mergeFrozenExternalIds(result.item.ids, candidates, warnings),
35
+ },
36
+ sources: mergeFrozenSources(result.sources, candidates),
37
+ };
38
+ });
39
+ }
40
+ // Refreshes final debug positions after snapshot recovery without changing candidate order.
41
+ // Обновляет итоговые debug-позиции после snapshot recovery без изменения порядка кандидатов.
42
+ export function finalizeSearchRankingEvidence(results) {
43
+ return results.map((result, index) => result.ranking
44
+ ? {
45
+ ...result,
46
+ ranking: {
47
+ ...result.ranking,
48
+ finalPosition: index + 1,
49
+ },
50
+ }
51
+ : result);
52
+ }
53
+ // Keeps ranked relevant identities first while retaining unresolved discovery candidates for aliases.
54
+ // Сохраняет ranked relevant identities первыми и оставляет unresolved candidates для aliases.
55
+ export function createFrozenDiscoveryResults(rankedResults, preliminaryResults) {
56
+ const frozen = [...rankedResults];
57
+ for (const candidate of preliminaryResults) {
58
+ if (!frozen.some((existing) => isSameDiscoveryCandidate(existing, candidate))) {
59
+ frozen.push(candidate);
60
+ }
61
+ }
62
+ return frozen;
63
+ }
64
+ // Selects bounded enrichment targets by preliminary discovery priority without changing response order.
65
+ // Выбирает bounded enrichment targets по preliminary priority без изменения порядка ответа.
66
+ export function createSearchEnrichmentCandidates(frozenResults, preliminaryResults) {
67
+ const candidates = [];
68
+ for (const preliminary of preliminaryResults) {
69
+ const frozen = frozenResults.find((result) => isSameDiscoveryCandidate(result, preliminary));
70
+ if (frozen && !candidates.includes(frozen)) {
71
+ candidates.push(frozen);
72
+ }
73
+ }
74
+ for (const frozen of frozenResults) {
75
+ if (!candidates.includes(frozen)) {
76
+ candidates.push(frozen);
77
+ }
78
+ }
79
+ return candidates;
80
+ }
81
+ // Removes candidates that remain unrelated after presentation enrichment without changing order.
82
+ // Удаляет нерелевантные после presentation enrichment candidates без изменения порядка.
83
+ export function filterFrozenSearchResults(results, query) {
84
+ const queryTitle = query.title?.trim();
85
+ if (!queryTitle) {
86
+ return results;
87
+ }
88
+ return results.filter((result) => {
89
+ const entry = {
90
+ result: {
91
+ provider: result.sources[0]?.provider ?? "search-discovery",
92
+ item: result.item,
93
+ },
94
+ index: 0,
95
+ };
96
+ return titleRelevanceScore([entry], queryTitle) > 0;
97
+ });
98
+ }
99
+ function createPresentationEntries(result, candidates) {
100
+ const base = {
101
+ provider: result.sources[0]?.provider ?? "search-discovery",
102
+ item: result.item,
103
+ };
104
+ return [base, ...candidates].map((providerResult, index) => ({
105
+ result: providerResult,
106
+ index,
107
+ }));
108
+ }
109
+ function arePresentationTypesCompatible(left, right) {
110
+ return (left.type === right.type ||
111
+ (left.type === "anime" && right.type === "series") ||
112
+ (left.type === "series" && right.type === "anime"));
113
+ }
114
+ function isSameDiscoveryCandidate(left, right) {
115
+ return ((left.item.type === right.item.type && left.item.id === right.item.id) ||
116
+ (left.item.type === right.item.type && hasSharedStrongId(left.item.ids, right.item.ids)));
117
+ }
118
+ function mergeFrozenExternalIds(discoveryIds, candidates, warnings) {
119
+ const merged = { ...discoveryIds };
120
+ for (const candidate of candidates) {
121
+ for (const key of EXTERNAL_ID_KEYS) {
122
+ const value = candidate.item.ids?.[key];
123
+ if (!value) {
124
+ continue;
125
+ }
126
+ const existing = merged[key];
127
+ if (!existing) {
128
+ merged[key] = value;
129
+ }
130
+ else if (existing !== value) {
131
+ warnings.push({
132
+ code: "EXTERNAL_ID_CONFLICT",
133
+ message: `Conflicting ${key} IDs during search enrichment; kept ${existing}.`,
134
+ provider: candidate.provider,
135
+ });
136
+ }
137
+ }
138
+ }
139
+ return Object.keys(merged).length > 0 ? merged : undefined;
140
+ }
141
+ function mergeFrozenSources(discoverySources, candidates) {
142
+ const sources = discoverySources.map((source) => structuredClone(source));
143
+ for (const candidate of candidates) {
144
+ const source = {
145
+ provider: candidate.source?.provider ?? candidate.provider,
146
+ ids: candidate.source?.ids ?? candidate.item.ids,
147
+ url: candidate.source?.url,
148
+ };
149
+ if (!sources.some((existing) => existing.provider === source.provider)) {
150
+ sources.push(source);
151
+ }
152
+ }
153
+ return sources;
154
+ }
155
+ //# sourceMappingURL=search-result-freeze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-result-freeze.js","sourceRoot":"","sources":["../../src/engine/search-result-freeze.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { ResponseMeta } from "../response/index.js";
2
+ interface ResponseWithMeta {
3
+ meta: ResponseMeta;
4
+ }
5
+ export interface StaleFallbackInput<T extends ResponseWithMeta> {
6
+ stale: T | undefined;
7
+ pending: Promise<T>;
8
+ tookMs: () => number;
9
+ }
10
+ export declare function loadWithStaleFallback<T extends ResponseWithMeta>(input: StaleFallbackInput<T>): Promise<T>;
11
+ export {};
@@ -0,0 +1,60 @@
1
+ import { MediaEngineError } from "../errors/index.js";
2
+ // Returns stale metadata only when every current provider failure is retryable.
3
+ // Возвращает устаревшие метаданные только при retryable-сбое всех текущих провайдеров.
4
+ export async function loadWithStaleFallback(input) {
5
+ try {
6
+ return await input.pending;
7
+ }
8
+ catch (error) {
9
+ const failed = getRetryableProviderFailures(error);
10
+ if (!input.stale || !failed) {
11
+ throw error;
12
+ }
13
+ const response = structuredClone(input.stale);
14
+ response.meta = {
15
+ providers: {
16
+ requested: failed.map((failure) => failure.provider),
17
+ successful: [],
18
+ failed,
19
+ },
20
+ cached: true,
21
+ stale: true,
22
+ tookMs: input.tookMs(),
23
+ warnings: [
24
+ {
25
+ code: "STALE_CACHE_FALLBACK",
26
+ message: "Returned stale cached data because all selected providers failed retryably.",
27
+ },
28
+ ],
29
+ debug: response.meta.debug
30
+ ? {
31
+ providers: failed.map((failure) => failure.provider),
32
+ timings: [],
33
+ }
34
+ : undefined,
35
+ };
36
+ return response;
37
+ }
38
+ }
39
+ function getRetryableProviderFailures(error) {
40
+ if (!(error instanceof MediaEngineError) || error.code !== "PROVIDER_ERROR") {
41
+ return undefined;
42
+ }
43
+ const cause = error.cause;
44
+ const failed = cause?.failed;
45
+ if (!Array.isArray(failed) || failed.length === 0 || !failed.every(isRetryableProviderFailure)) {
46
+ return undefined;
47
+ }
48
+ return failed;
49
+ }
50
+ function isRetryableProviderFailure(value) {
51
+ if (typeof value !== "object" || value === null) {
52
+ return false;
53
+ }
54
+ const failure = value;
55
+ return (typeof failure.provider === "string" &&
56
+ typeof failure.code === "string" &&
57
+ failure.retryable === true &&
58
+ typeof failure.message === "string");
59
+ }
60
+ //# sourceMappingURL=stale-fallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stale-fallback.js","sourceRoot":"","sources":["../../src/engine/stale-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAatD,gFAAgF;AAChF,uFAAuF;AACvF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAA4B;IAE5B,IAAI,CAAC;QACH,OAAO,MAAM,KAAK,CAAC,OAAO,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,4BAA4B,CAAC,KAAK,CAAC,CAAC;QAEnD,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5B,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC9C,QAAQ,CAAC,IAAI,GAAG;YACd,SAAS,EAAE;gBACT,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;gBACpD,UAAU,EAAE,EAAE;gBACd,MAAM;aACP;YACD,MAAM,EAAE,IAAI;YACZ,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;YACtB,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,sBAAsB;oBAC5B,OAAO,EAAE,6EAA6E;iBACvF;aACF;YACD,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK;gBACxB,CAAC,CAAC;oBACE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;oBACpD,OAAO,EAAE,EAAE;iBACZ;gBACH,CAAC,CAAC,SAAS;SACd,CAAC;QAEF,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,SAAS,4BAA4B,CAAC,KAAc;IAClD,IAAI,CAAC,CAAC,KAAK,YAAY,gBAAgB,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;QAC5E,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAyC,CAAC;IAC9D,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,CAAC;IAE7B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,EAAE,CAAC;QAC/F,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,0BAA0B,CAAC,KAAc;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,OAAO,GAAG,KAAiC,CAAC;IAClD,OAAO,CACL,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ;QACpC,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;QAChC,OAAO,CAAC,SAAS,KAAK,IAAI;QAC1B,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,CACpC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare class ProviderTimeoutBudget {
2
+ private readonly resolveTimeoutMs;
3
+ private readonly now;
4
+ private readonly deadlines;
5
+ constructor(resolveTimeoutMs: (provider: string) => number | undefined, now?: () => number);
6
+ getRemainingMs(provider: string, callLimitMs?: number): number | undefined;
7
+ }
@@ -0,0 +1,31 @@
1
+ // Shares one timeout deadline across all calls to a provider in one engine operation.
2
+ // Делит один timeout deadline между всеми вызовами провайдера в одной операции движка.
3
+ export class ProviderTimeoutBudget {
4
+ resolveTimeoutMs;
5
+ now;
6
+ deadlines = new Map();
7
+ constructor(resolveTimeoutMs, now = Date.now) {
8
+ this.resolveTimeoutMs = resolveTimeoutMs;
9
+ this.now = now;
10
+ }
11
+ getRemainingMs(provider, callLimitMs) {
12
+ const now = this.now();
13
+ const existingDeadline = this.deadlines.get(provider);
14
+ if (existingDeadline !== undefined) {
15
+ return applyCallLimit(Math.max(0, existingDeadline - now), callLimitMs);
16
+ }
17
+ const timeoutMs = this.resolveTimeoutMs(provider);
18
+ const initialBudget = applyCallLimit(timeoutMs, callLimitMs);
19
+ if (initialBudget !== undefined) {
20
+ this.deadlines.set(provider, now + initialBudget);
21
+ }
22
+ return initialBudget;
23
+ }
24
+ }
25
+ function applyCallLimit(timeoutMs, callLimitMs) {
26
+ if (timeoutMs === undefined) {
27
+ return callLimitMs;
28
+ }
29
+ return callLimitMs === undefined ? timeoutMs : Math.min(timeoutMs, callLimitMs);
30
+ }
31
+ //# sourceMappingURL=timeout-budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeout-budget.js","sourceRoot":"","sources":["../../src/engine/timeout-budget.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,uFAAuF;AACvF,MAAM,OAAO,qBAAqB;IAIb;IACA;IAJF,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEvD,YACmB,gBAA0D,EAC1D,MAAoB,IAAI,CAAC,GAAG;QAD5B,qBAAgB,GAAhB,gBAAgB,CAA0C;QAC1D,QAAG,GAAH,GAAG,CAAyB;IAC5C,CAAC;IAEJ,cAAc,CAAC,QAAgB,EAAE,WAAoB;QACnD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAEtD,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;YACnC,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,gBAAgB,GAAG,GAAG,CAAC,EAAE,WAAW,CAAC,CAAC;QAC1E,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,cAAc,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAE7D,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,GAAG,aAAa,CAAC,CAAC;QACpD,CAAC;QAED,OAAO,aAAa,CAAC;IACvB,CAAC;CACF;AAED,SAAS,cAAc,CACrB,SAA6B,EAC7B,WAA+B;IAE/B,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AAClF,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { Cache, CacheSetOptions } from "../cache/index.js";
2
+ import type { TorrentDiscoveryQuery, TorrentDiscoveryResponse, TorrentProvider } from "../torrent/index.js";
3
+ import type { MediaEngineOperationOptions } from "./types.js";
4
+ import type { ProviderCircuitBreaker } from "./circuit-breaker.js";
5
+ import type { ProviderConcurrencyLimiter } from "./concurrency-limiter.js";
6
+ import type { InFlightRequestCoalescer } from "./in-flight.js";
7
+ export interface TorrentDiscoveryOperationContext {
8
+ query: TorrentDiscoveryQuery;
9
+ options: MediaEngineOperationOptions;
10
+ startedAt: number;
11
+ providers: TorrentProvider[];
12
+ cache?: Cache;
13
+ debug: boolean;
14
+ circuitBreaker?: ProviderCircuitBreaker;
15
+ concurrencyLimiter?: ProviderConcurrencyLimiter;
16
+ inFlightRequests: InFlightRequestCoalescer;
17
+ getProviderTimeoutMs(providerName: string): number | undefined;
18
+ }
19
+ export declare function executeTorrentDiscovery(context: TorrentDiscoveryOperationContext): Promise<TorrentDiscoveryResponse>;
20
+ export declare function selectTorrentProviders(providers: TorrentProvider[], query: TorrentDiscoveryQuery): TorrentProvider[];
21
+ export declare function mergeTorrentDiscoveryResults(query: TorrentDiscoveryQuery, results: TorrentDiscoveryResponse[]): TorrentDiscoveryResponse;
22
+ export declare function createTorrentCacheOptions(response: TorrentDiscoveryResponse): CacheSetOptions | undefined;
@@ -0,0 +1,188 @@
1
+ import { MediaEngineError } from "../errors/index.js";
2
+ import { createTorrentDiscoveryCacheKey, sortObject } from "./query.js";
3
+ import { throwIfAborted, waitForCaller } from "./operation.js";
4
+ import { callTimedTorrentProvider } from "./provider-calls.js";
5
+ import { createResponseMeta, elapsedSince } from "./response-meta.js";
6
+ import { ProviderTimeoutBudget } from "./timeout-budget.js";
7
+ const EXPIRING_TORRENT_CACHE_SAFETY_MS = 1_000;
8
+ // Executes the complete cache/coalescing/provider lifecycle for torrent discovery.
9
+ // Выполняет полный cache/coalescing/provider lifecycle для torrent discovery.
10
+ export async function executeTorrentDiscovery(context) {
11
+ const { query, options, startedAt } = context;
12
+ if (query.limit === 0) {
13
+ const response = mergeTorrentDiscoveryResults(query, []);
14
+ response.meta = createResponseMeta({
15
+ requested: [],
16
+ successful: [],
17
+ failed: [],
18
+ warnings: [],
19
+ cached: false,
20
+ tookMs: elapsedSince(startedAt),
21
+ debug: context.debug,
22
+ timings: [],
23
+ });
24
+ return response;
25
+ }
26
+ const cacheKey = createTorrentDiscoveryCacheKey(query);
27
+ const cached = await waitForCaller(context.cache?.get(cacheKey), options.signal);
28
+ if (cached) {
29
+ const response = structuredClone(cached);
30
+ return {
31
+ ...response,
32
+ query,
33
+ meta: response.meta
34
+ ? {
35
+ ...response.meta,
36
+ cached: true,
37
+ tookMs: elapsedSince(startedAt),
38
+ }
39
+ : undefined,
40
+ };
41
+ }
42
+ const inFlight = context.inFlightRequests.forCaller(options);
43
+ return inFlight.run(`torrents:${cacheKey}`, async (operationSignal) => {
44
+ const timeoutBudget = new ProviderTimeoutBudget(context.getProviderTimeoutMs);
45
+ const providers = selectTorrentProviders(context.providers, query);
46
+ const requested = providers.map((provider) => provider.name);
47
+ const successful = [];
48
+ const failed = [];
49
+ const providerResults = [];
50
+ const providerTimings = [];
51
+ const outcomes = await Promise.all(providers.map((provider) => callTimedTorrentProvider(provider, query, {
52
+ debug: context.debug,
53
+ language: query.language,
54
+ signal: operationSignal,
55
+ timeoutMs: timeoutBudget.getRemainingMs(provider.name),
56
+ circuitBreaker: context.circuitBreaker,
57
+ concurrencyLimiter: context.concurrencyLimiter,
58
+ })));
59
+ for (const outcome of outcomes) {
60
+ providerTimings.push(outcome.timing);
61
+ if (outcome.failure) {
62
+ failed.push(outcome.failure);
63
+ }
64
+ else {
65
+ successful.push(outcome.provider);
66
+ if (outcome.result) {
67
+ providerResults.push(outcome.result);
68
+ }
69
+ }
70
+ }
71
+ if (providers.length > 0 && failed.length === providers.length) {
72
+ throw new MediaEngineError({
73
+ code: "PROVIDER_ERROR",
74
+ message: "All torrent providers failed.",
75
+ cause: { failed },
76
+ });
77
+ }
78
+ const response = mergeTorrentDiscoveryResults(query, providerResults);
79
+ response.meta = createResponseMeta({
80
+ requested,
81
+ successful,
82
+ failed,
83
+ warnings: [],
84
+ cached: false,
85
+ tookMs: elapsedSince(startedAt),
86
+ debug: context.debug,
87
+ timings: providerTimings,
88
+ });
89
+ throwIfAborted(operationSignal);
90
+ if (!failed.some((failure) => failure.retryable)) {
91
+ await context.cache?.set(cacheKey, structuredClone(response), createTorrentCacheOptions(response));
92
+ }
93
+ return response;
94
+ });
95
+ }
96
+ // Selects torrent providers that can answer the normalized discovery query.
97
+ // Выбирает torrent-провайдеры, способные ответить на нормализованный discovery-запрос.
98
+ export function selectTorrentProviders(providers, query) {
99
+ return providers.filter((provider) => {
100
+ if (query.providers && !query.providers.includes(provider.name)) {
101
+ return false;
102
+ }
103
+ if (!provider.capabilities.mediaTypes.includes(query.type)) {
104
+ return false;
105
+ }
106
+ if (hasEpisodeQuery(query) && !provider.capabilities.lookup.byEpisode) {
107
+ return false;
108
+ }
109
+ return (Boolean(query.title && provider.capabilities.lookup.byTitle) ||
110
+ hasSupportedExternalId(query.ids, provider.capabilities.lookup.byExternalIds));
111
+ });
112
+ }
113
+ // Merges torrent discovery results without hiding provider attribution.
114
+ // Объединяет torrent discovery результаты, не скрывая атрибуцию провайдеров.
115
+ export function mergeTorrentDiscoveryResults(query, results) {
116
+ const candidates = interleaveUniqueCandidates(results);
117
+ return {
118
+ query,
119
+ item: results.find((result) => result.item)?.item,
120
+ candidates: query.limit === undefined ? candidates : candidates.slice(0, query.limit),
121
+ sourceProviders: uniqueBy(results.flatMap((result) => result.sourceProviders), (source) => createTorrentSourceKey(source)),
122
+ checkedAt: new Date().toISOString(),
123
+ };
124
+ }
125
+ // Interleaves configured provider results so a global limit preserves source diversity.
126
+ // Чередует результаты провайдеров, чтобы глобальный limit сохранял разнообразие источников.
127
+ function interleaveUniqueCandidates(results) {
128
+ const candidates = [];
129
+ const seen = new Set();
130
+ const maximumLength = Math.max(0, ...results.map((result) => result.candidates.length));
131
+ for (let index = 0; index < maximumLength; index += 1) {
132
+ for (const result of results) {
133
+ const candidate = result.candidates[index];
134
+ if (!candidate) {
135
+ continue;
136
+ }
137
+ const key = createTorrentCandidateKey(candidate);
138
+ if (!seen.has(key)) {
139
+ seen.add(key);
140
+ candidates.push(candidate);
141
+ }
142
+ }
143
+ }
144
+ return candidates;
145
+ }
146
+ // Keeps cached handoff data from outliving its earliest advertised expiration.
147
+ // Не позволяет кешированным handoff-данным пережить ближайший заявленный срок действия.
148
+ export function createTorrentCacheOptions(response) {
149
+ const expirations = response.candidates
150
+ .map((candidate) => candidate.expiresAt)
151
+ .filter((value) => value !== undefined)
152
+ .map((value) => Date.parse(value))
153
+ .filter(Number.isFinite);
154
+ if (expirations.length === 0) {
155
+ return { staleTtlMs: 0 };
156
+ }
157
+ return {
158
+ ttlMs: Math.max(0, Math.min(...expirations) - Date.now() - EXPIRING_TORRENT_CACHE_SAFETY_MS),
159
+ staleTtlMs: 0,
160
+ };
161
+ }
162
+ function hasSupportedExternalId(ids, supportedSources) {
163
+ return Boolean(ids && supportedSources.some((source) => Boolean(ids[source])));
164
+ }
165
+ function hasEpisodeQuery(query) {
166
+ return (query.seasonNumber !== undefined ||
167
+ query.episodeNumber !== undefined ||
168
+ query.absoluteEpisodeNumber !== undefined);
169
+ }
170
+ function createTorrentCandidateKey(candidate) {
171
+ return `${candidate.provider}:${candidate.id}`;
172
+ }
173
+ function createTorrentSourceKey(source) {
174
+ return `${source.provider}:${source.url ?? ""}:${JSON.stringify(sortObject(source.ids ?? {}))}`;
175
+ }
176
+ function uniqueBy(values, getKey) {
177
+ const seen = new Set();
178
+ const unique = [];
179
+ for (const value of values) {
180
+ const key = getKey(value);
181
+ if (!seen.has(key)) {
182
+ seen.add(key);
183
+ unique.push(value);
184
+ }
185
+ }
186
+ return unique;
187
+ }
188
+ //# sourceMappingURL=torrents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"torrents.js","sourceRoot":"","sources":["../../src/engine/torrents.ts"],"names":[],"mappings":"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"}