1+ import { log } from 'apify' ;
12import type { ActorDefinition } from 'apify-client' ;
23
34import { ApifyClient , getApifyAPIBaseUrl } from '../apify-client.js' ;
@@ -8,7 +9,7 @@ export async function isActorMCPServer(actorID: string, apifyToken: string): Pro
89}
910
1011export async function getActorsMCPServerPath ( actorID : string , apifyToken : string ) : Promise < string | undefined > {
11- const actorDefinition = await getActorDefinition ( actorID , apifyToken ) ;
12+ const actorDefinition = await getFullActorDefinition ( actorID , apifyToken ) ;
1213
1314 if ( 'webServerMcpPath' in actorDefinition && typeof actorDefinition . webServerMcpPath === 'string' ) {
1415 return actorDefinition . webServerMcpPath ;
@@ -26,20 +27,32 @@ export async function getActorsMCPServerURL(actorID: string, apifyToken: string)
2627 return `${ standbyUrl } ${ mcpPath } ` ;
2728}
2829
30+ // ID does not change, so no TTL
31+ export const actorIDCache : Record < string , string > = { } ;
2932/**
3033* Gets Actor ID from the Actor object.
3134*
3235* @param actorID
3336* @param apifyToken
3437*/
3538export async function getRealActorID ( actorID : string , apifyToken : string ) : Promise < string > {
39+ if ( actorIDCache [ actorID ] ) {
40+ log . debug ( `Actor ${ actorID } ID cache hit` ) ;
41+ return actorIDCache [ actorID ] ;
42+ }
43+ log . debug ( `Actor ${ actorID } ID cache miss` ) ;
44+
3645 const apifyClient = new ApifyClient ( { token : apifyToken } ) ;
3746
3847 const actor = apifyClient . actor ( actorID ) ;
3948 const info = await actor . get ( ) ;
4049 if ( ! info ) {
4150 throw new Error ( `Actor ${ actorID } not found` ) ;
4251 }
52+
53+ if ( ! actorIDCache [ actorID ] ) {
54+ actorIDCache [ actorID ] = info . id ;
55+ }
4356 return info . id ;
4457}
4558
@@ -56,7 +69,29 @@ export async function getActorStandbyURL(actorID: string, apifyToken: string, st
5669 return `https://${ actorRealID } .${ standbyBaseUrl } ` ;
5770}
5871
59- export async function getActorDefinition ( actorID : string , apifyToken : string ) : Promise < ActorDefinition > {
72+ export const actorDefinitionCache : Record < string , {
73+ timestamp : number ;
74+ definition : ActorDefinition ;
75+ } > = { } ;
76+ export const ACTOR_DEFINITION_CACHE_TTL_MS = 1000 * 60 * 60 ; // 1 hour
77+ /**
78+ * Gets full Actor definition from the Apify API.
79+ */
80+ export async function getFullActorDefinition ( actorID : string , apifyToken : string ) : Promise < ActorDefinition > {
81+ const cacheInTTL = Date . now ( ) - ( actorDefinitionCache [ actorID ] ?. timestamp || 0 ) < ACTOR_DEFINITION_CACHE_TTL_MS ;
82+ // Hit the cache
83+ if ( actorDefinitionCache [ actorID ]
84+ && cacheInTTL ) {
85+ log . debug ( `Actor ${ actorID } definition cache hit` ) ;
86+ return actorDefinitionCache [ actorID ] . definition ;
87+ }
88+ // Refresh the cache after TTL expired
89+ if ( actorDefinitionCache [ actorID ] && ! cacheInTTL ) {
90+ log . debug ( `Actor ${ actorID } definition cache TTL expired, re-fetching` ) ;
91+ } else {
92+ log . debug ( `Actor ${ actorID } definition cache miss` ) ;
93+ }
94+
6095 const apifyClient = new ApifyClient ( { token : apifyToken } ) ;
6196 const actor = apifyClient . actor ( actorID ) ;
6297 const info = await actor . get ( ) ;
@@ -84,5 +119,14 @@ export async function getActorDefinition(actorID: string, apifyToken: string): P
84119 throw new Error ( `Actor default build ${ actorID } does not have Actor definition` ) ;
85120 }
86121
122+ // If the Actor is public, we cache the definition
123+ // This code branch is executed only on cache miss, so we know the cache entry is empty
124+ if ( info . isPublic ) {
125+ actorDefinitionCache [ actorID ] = {
126+ timestamp : Date . now ( ) ,
127+ definition : actorDefinition ,
128+ } ;
129+ }
130+
87131 return actorDefinition ;
88132}
0 commit comments