Autotask: Migration to Typescript

I am trying to make the migration of my autotask from javascript to typescript. My autotask is designed to use sentinel event triggers to run (and additionally extract information from the event to process in the autotask).

Initially, the design I used to run locally was to pass the event object into the handler the way it would receive it if triggered by the sentinel:

if (require.main === module) {
  require("dotenv").config();
  const { RELAYER_KEY: apiKey, RELAYER_SECRET: apiSecret } = process.env;
  const event = {
      apiKey,
      apiSecret,
      request: {
        body: {
          matchReasons: [
            {},
            {
              params: {
                param1,
                param2,
                param3,
              },
            },
          ],
        },
      },
    };
  exports
    .handler(iCredentials)
    .then(() => process.exit(0))
    .catch((error) => {
      console.error(error);
      process.exit(1);
    });
}

My handler begins with identifying whether or not the autotask was triggered via an even trigger (sentinel) by checking if there is a "request" key (my understanding is that this key is omitted when autotask is manually invoked).

export async function handler(event) {
  if (Object.prototype.hasOwnProperty.call(event, "request")) {
   const params = event.request.body.matchReasons[1].params
  } else {
   param1 = defaultValue1;
   param2 = defaultValue2;
   param3 = defaultValue3;
  }
}

My conversion to typescript got this far.

export async function handler(event: AutotaskEvent & RelayerParams) {
  if (Object.prototype.hasOwnProperty.call(event, "request")) {
    const trigger = event.request.body as SentinelTriggerEvent
    const paramsObject = trigger.matchReasons[1] as unknown as IParams
    const params = paramsObject.params
  } else {
   param1 = defaultValue1;
   param2 = defaultValue2;
   param3 = defaultValue3;
  }
}

export interface IParams {
  params: {
    param1: string,
    param2: number,
    param3: string,
  },
}

I am wondering if there is a more elegant solution than the 'unknown' type declaration in this case.

Hey @Guceri! The matchReasons array is of type SentinelConditionSummary, so you can just cast to that. Check out the defender-autotask-utils package, which exports these types.

export interface SentinelTriggerEvent {
  hash: string;
  timestamp: number;
  blockNumber: string;
  blockHash: string;
  transaction: EthReceipt;
  matchReasons: SentinelConditionSummary[];
  sentinel: SentinelSubscriberSummary;
  metadata?: { [k: string]: unknown };
}

export type SentinelConditionSummary =
  | TransactionConditionSummary
  | InternalFunctionConditionSummary
  | FunctionConditionSummary
  | EventConditionSummary;

interface SentinelBaseConditionSummary {
  condition?: string;
}

interface SentinelBaseAbiConditionSummary extends SentinelBaseConditionSummary {
  signature: string;
  args: any[];
  params: { [key: string]: any };
}

interface EventConditionSummary extends SentinelBaseAbiConditionSummary {
  type: 'event';
}

interface FunctionConditionSummary extends SentinelBaseAbiConditionSummary {
  type: 'function';
}

interface InternalFunctionConditionSummary extends SentinelBaseAbiConditionSummary {
  type: 'internal-function';
}

interface TransactionConditionSummary extends SentinelBaseConditionSummary {
  type: 'transaction';
}

Thanks Santiago,
The end goal here is to get the params from the SentinelTriggerEvent. The matchReasons are a SentinelConditionSummary array (which is also an export), but this is where it is becomes difficult to get the typings any further.

Below I am able to coerce the SentinelTriggerEvent type without an issue as it is an export.
Screen Shot 2021-10-12 at 1.40.37 PM

I can then target the matchReasons and the typing is correct as you can see (a SentinelConditionSummary array)

Screen Shot 2021-10-12 at 1.41.56 PM

The SentinelConditionSummary has 4 structure types, but I can not seem to access them to get to params. It does not provide me with the optionality. Since SentinelBaseAbiConditionSummary is not an export, I can not coerce it either.

Screen Shot 2021-10-12 at 1.51.25 PM

To help be a little more specific, here is another example of me trying to get the params key that I am looking for.

It is showing that this path is of type TransactionConditionSummary. It happens to be the only type that does not extend to SentinelBaseAbiConditionSummary which is what I believe would eliminate the issue.

Yes, the typescript compiler needs to know that that matchReason is not a Transaction one, since that type within the union does not have params. You can handle that by doing something like:

const trigger = event.request.body as SentinelTriggerEvent;
const match = trigger.matchReasons[1];
if (match.type === 'transaction') throw new Error(`Unexpected match reason`);
// Here the compiler knows that the type is not Transaction, so params are accessible
const params = match.params;

Check out discriminated unions in typescript for more info on how this works!

@spalladino thank you as always for being helpful and providing the reference link!

1 Like