> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stratumn.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Effects

## What are the effects?

Effects are the business logic written in TypeScript that is executed by Trace API
after an [action](/configuration/action) is submitted.

The effects declared in the configuration project are stringified functions that are stored in the database,
so they can be interpreted at runtime by Trace API.

<Info>No external constants or functions can be used in the effects.</Info>

### Workflow definitions

The `dsl.$definitions` object is the read-only storage of the workflow. It can be used to store constants, environment variables, workflow ids and stringified functions that will be accessible in the effects.
The definitions can be of any shape, but by convention, it's shape is the following:

<Tabs>
  <Tab title="Documentation">
    <ParamField path="repo" type="object">
      The repository of the workflow where workflow constants are stored.
    </ParamField>

    <ParamField path="functions" type="object">
      An object where each key is the name of the function and the value is the stringified function.
    </ParamField>

    <ParamField path="wfIds" type="object">
      A map between the workflow label and its id.
    </ParamField>

    <ParamField path="env" type="object">
      A map between the environment variable name and its value.
    </ParamField>
  </Tab>

  <Tab title="Example">
    ```ts Example of a workflow definitions theme={null}
    {
      repo: {
        prices: {
          pizza: 10,
          salad: 5,
        }
      },
      functions: {
        clone: 'function clone(value) { return JSON.parse(JSON.stringify(value)); }',
      },
      wfIds: {
        restaurant: '10'
      },
      env: {
        STRATUMN_API_URL: 'https://api.stratum.com',
        STAGE: 'release',
        ...
      },
    }
    ```
  </Tab>

  <Tab title="Usage">
    ```ts Usage in effects theme={null}
    const { repo, functions, wfIds, env } = dsl.$definitions;
    const { clone } = functions;

    const pizzaPrice = repo.prices.pizza;
    const clonedPrices = clone(repo.prices);
    ```
  </Tab>
</Tabs>

### Notifications

Notifications (usually emails) are sent to a user (could be a single user, or a group) when performing an action. They are sent when the `state.notifications` is updated. Thus, notifications are usually built and sent inside the [effects](/configuration/action/effects) of an action.
There is 3 steps to send a notification through configuration :

1. Create a function inside `workflowDefinitions` to build the template of the email, and manipulate data derived from the state (because no external constants or functions can be used in the effects.)

```typescript create a custom function in the workflow /definitions/functions theme={null}
export const createNotification = (data, meta) => {
  return {
    title: '',
    channel: 'EMAIL',
    groupLabel: data.entityLabel,
    template: {
      // etc..
    }
  };
};
```

2. Inside the effects, import this function.

```typescript Usage in effects theme={null}
const { state, formData, meta } = dsl.$variables;
const { createNotification } = dsl.$functions;

const notificationPayload = createNotification(state.data, meta);
```

3. Updating `state.notifications` by pushing the template object with the correct data.

```typescript Update state theme={null}
state.notifications.push(notificationPayload);
```

To see more details about it, go to the [notification](/configuration/action/notifications) page.

### The `Stratumn` constant

You can use the `Stratumn` constant to get access to `Sentry`, a Pino `logger` and the `StratumnError` class.

Unlike other variables, this constant does not need to be created or imported, it is accessible globally in the effects.

<AccordionGroup>
  <Accordion title="Sentry" icon="eye">
    The Sentry SDK, allowing you to [capture errors](https://docs.sentry.io/platforms/javascript/guides/node/usage/#capturing-errors), wrap functions with [spans](https://docs.sentry.io/platforms/javascript/guides/node/tracing/instrumentation/#starting-an-active-span-startspan), add [context](https://docs.sentry.io/platforms/javascript/guides/node/configuration/apis/#setContext), set [tags](https://docs.sentry.io/platforms/javascript/guides/node/enriching-events/tags/), add [metrics](https://docs.sentry.io/platforms/javascript/guides/node/metrics/#usage) etc.

    This variable is the same as if you did `import * as Sentry from "@sentry/node";` in your code.

    ```typescript Usage your effect function theme={null}
    const { Sentry } = Stratumn;

    try {
      aFunctionThatMightFail();
    } catch (error: unknown) {
      Sentry.captureException(error);
    }
    ```
  </Accordion>

  <Accordion title="Pino logger" icon="line-columns">
    A [Pino logger instance](https://getpino.io/#/docs/api?id=logger-instance), allowing you to log messages with different levels.

    ```typescript Usage your effect function theme={null}
    const { logger } = Stratumn;

    logger.info('Hello, world!');
    logger.info({name: 'John Doe'}, 'Hello, John Doe!');

    logger.warn('This is a warning');
    logger.error({ error: error }, error instanceof Error ? error.message : 'Unknown error');
    logger.fatal('This is a fatal error');
    logger.debug('This is a debug message');
    ```
  </Accordion>

  <Accordion title="Stratumn Error" icon="triangle-exclamation">
    The Stratumn Error class, allowing you to create custom errors with a code and a message.

    <Tabs>
      <Tab title="Parameters">
        <ParamField path="name" type="string" required>
          The name of the error.
        </ParamField>

        <ParamField path="code" type="string" required>
          The code of the error.
        </ParamField>

        <ParamField path="status" type="string literal" required>
          The status of the error.

          <Expandable title="Status values">
            | Status                  | Code | Description            |
            | ----------------------- | ---- | ---------------------- |
            | BAD\_REQUEST            | 400  | Bad request.           |
            | UNAUTHORIZED            | 401  | Unauthorized error.    |
            | FORBIDDEN               | 403  | Forbidden error.       |
            | NOT\_FOUND              | 404  | Not found error.       |
            | NOT\_IMPLEMENTED        | 501  | Not implemented error. |
            | INTERNAL\_SERVER\_ERROR | 500  | Internal server error. |
          </Expandable>
        </ParamField>

        <ParamField path="message" type="string" required>
          The message of the error.
        </ParamField>

        <ParamField path="context" type="object" required>
          The context of the error. It can be used to add additional data to the error.
        </ParamField>
      </Tab>

      <Tab title="Example">
        ```typescript Usage your effect function theme={null}
        const { StratumnError } = Stratumn;

        throw new StratumnError(
          'execJs/my-custom-error',
          'fc6d0328', // <-- A random error code
          'BAD_REQUEST',
          'My custom error message',
          {
            // ... any additional data you want to add to the error
          }
        );
        ```
      </Tab>
    </Tabs>
  </Accordion>
</AccordionGroup>

### External functions

Some functions are declared directly in Trace API, and can be accessed direcly in the effects using `dsl.$modules`.

<AccordionGroup>
  <Accordion title="createLink" icon="link">
    An asynchronous function designed to create a new link,
    enabling Trace to automatically perform an action.
    This action can be executed within any workflow,
    either by creating a new trace or targeting an existing one.

    <Tabs>
      <Tab title="Body">
        <ParamField path="workflowId" type="string" required>
          The id of the target workflow.
        </ParamField>

        <ParamField path="traceId" type="string">
          The id of the target trace.\
          If not provided, the link will be created in a new trace.
        </ParamField>

        <ParamField path="action" type="string" required>
          The action that will be done.
        </ParamField>

        <ParamField path="formData" type="object" required>
          The form data to be used in the action.
        </ParamField>

        <ParamField path="groupLabel" type="string" required>
          The label of the group that will do the automatic action.\
          Since `createLink` makes an automated action, the group label is usually the `'traceBot'`.
        </ParamField>

        <ParamField path="createdAt" type="string" required>
          The date of creation of the link.
        </ParamField>
      </Tab>

      <Tab title="Example">
        ```typescript theme={null}
        const newLink = await dsl.$modules.createLink({
          action: 'orderMeal',
          workflowId: dsl.$definitions.wfIds.restaurant,
          formData: {
            meal: 'Pizza'
          },
          groupLabel: 'traceBot',
        });
        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="searchTraces" icon="magnifying-glass">
    An asynchronous function that searches for traces, allowing optional filters to be applied based on the traces' data.

    <Tabs>
      <Tab title="Body">
        <ParamField path="workflowId" type="string" required>
          The id of the workflow to search traces in.
        </ParamField>

        <ParamField path="filters" type="JSONDeepFilterValue[]">
          The filters to apply to the search.

          <Expandable title="JSONDeepFilterValue type" defaultOpen={false}>
            <Tabs>
              <Tab title="TextFilterValue">
                <ParamField path="type" type="literal" required>
                  The type of the filter, must be equal to `'text'`.
                </ParamField>

                <ParamField path="path" type="JMESPath" required>
                  The JMESPath to apply to the filter.
                </ParamField>

                <ParamField path="value" type="string" required>
                  The value to filter on.
                </ParamField>

                <ParamField path="exact" type="boolean">
                  Whether the filter should be an exact match.
                </ParamField>

                <ParamField path="not" type="boolean">
                  Whether the filter should be a negative match.
                </ParamField>
              </Tab>

              <Tab title="NumberFilterValue">
                <ParamField path="type" type="literal" required>
                  The type of the filter, must be equal to `'number'`.
                </ParamField>

                <ParamField path="path" type="JMESPath" required>
                  The JMESPath to apply to the filter.
                </ParamField>

                <ParamField path="value" type="string" required>
                  The value to filter on.
                </ParamField>
              </Tab>

              <Tab title="DateFilterValue">
                <ParamField path="type" type="literal" required>
                  The type of the filter, must be equal to `'date'`.
                </ParamField>

                <ParamField path="path" type="JMESPath" required>
                  The JMESPath to apply to the filter.
                </ParamField>

                <ParamField path="value" type="string" required>
                  The value to filter on.
                </ParamField>

                <ParamField path="format" type="string">
                  The format of the stored date to filter on.
                </ParamField>

                <ParamField path="inputFormat" type="string">
                  The format of the provided date value.
                </ParamField>
              </Tab>
            </Tabs>
          </Expandable>
        </ParamField>
      </Tab>

      <Tab title="Example">
        ```typescript Get all traces where the meal is 'Pizza' theme={null}
        const res = await dsl.$modules.searchTraces({
          workflowId: dsl.$definitions.wfIds.restaurant,
          filters: [
            {
              type: 'text',
              path: 'data.meal',
              value: 'Pizza'
            }
          ]
        });
        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="createScheduler" icon="timer">
    An asynchronous function used to create a new scheduler.\
    The scheduler can be for example used to create links at a specific interval.

    <Tabs>
      <Tab title="Body">
        <ParamField path="name" type="string" required>
          The unique name of the scheduler.
        </ParamField>

        <ParamField path="data" type="object">
          The data that will be used by the scheduler.
        </ParamField>

        <ParamField path="cron_expression" type="string" required>
          The [cron expression](https://crontab.guru/) that defines the schedule.
        </ParamField>

        <ParamField path="created_by" type="string" required>
          Any string to identify the creator of the scheduler.
        </ParamField>
      </Tab>

      <Tab title="Example">
        ```typescript Creates a link in the current trace every day theme={null}
        const scheduler = await dsl.$modules.createScheduler({
          name: `restaurant-${meta.traceName}`,
          data: {
            webhook_url: `${STRATUMN_API_URL}/v2/traces/${meta.traceId}/links`,
            webhook_payload: {
              groupLabel: 'traceBot',
              actionKey: 'orderMeal',
              traceInput: {
                meal: 'Pizza'
              },
              workflowId: dsl.$definitions.wfIds.restaurant
            }
          },
          cron_expression: '0 0 * * *',
          created_by: `restaurant` satisfies WorkflowLabel
        });
        // We store the scheduler infos in the state to be able to stop it later
        state.data.scheduler = scheduler;
        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="stopScheduler" icon="stop">
    An asynchronous function that stops a scheduler using its id.

    <Tabs>
      <Tab title="Body">
        <ParamField path="id" type="string" required>
          The id of the scheduler to stop.
        </ParamField>
      </Tab>

      <Tab title="Example">
        ```typescript stopScheduler example theme={null}
        await dsl.$modules.stopScheduler({
          id: data.scheduler.infos.id
        });
        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="moment" icon="clock">
    Exposes the `moment` function from the moment library [*(v2.29.1)*](https://www.npmjs.com/package/moment/v/2.29.1).\
    See the [moment documentation](https://momentjs.com/docs/#/use-it/) for more information.

    ```typescript Example theme={null}
    const { moment } = dsl.$modules;
    const now = moment();
    const tomorrow = moment().add(1, 'day');
    const yesterday = moment().subtract(1, 'day');
    ```
  </Accordion>

  <Accordion title="zod" icon="shield-check">
    Exposes `z` from the zod library [*(v3.24.0)*](https://www.npmjs.com/package/zod/v/3.24.0).\
    See the [zod documentation](https://zod.dev/) for more information.

    ```typescript Example theme={null}
    const { z } = dsl.$modules;

    const isAdultSchema = z.object({
      name: z.string().min(2),
      age: z.number().min(18),
    }).strict();

    const myData = mySchema.parse({
      name: 'John',
      age: 30,
    });

    ```
  </Accordion>

  <Accordion title="generatePdf" icon="file-pdf">
    An asynchronous function that generates a PDF or a DOCX, PPTX, Excel based on a template and provided data.

    <Tabs>
      <Tab title="Body">
        <ParamField path="generatedFileName" type="string" required>
          The name of the generated PDF file.
        </ParamField>

        <ParamField path="templateKey" type="string" required>
          The key of the PDF template to use, as defined in the [workflow configuration](/configuration/introduction#workflow-configuration).
        </ParamField>

        <ParamField path="traceId" type="string" required>
          The id of the trace where the PDF will be stored.
        </ParamField>

        <ParamField path="fillData" type="PdfFieldData[] | Record<string, unknown>" required>
          The data to fill the PDF template with.
        </ParamField>

        <ParamField path="version" type="'pdf-lib' | 'docx-templater'" required>
          The engine to use for PDF generation.
        </ParamField>
      </Tab>

      <Tab title="Example (pdf-lib)">
        ```typescript theme={null}
        const { generatePdf } = dsl.$modules;

        const { generatedFileInfo } = await generatePdf({
          generatedFileName: 'my-generated-pdf.pdf',
          templateKey: 'simpleExampleTemplate',
          traceId: dsl.$variables.meta.traceId,
          fillData: {
            name: 'Name',
            value: 'Stratumn',
            type: 'text',
          },
          version: 'pdf-lib',
        });
        ```
      </Tab>

      <Tab title="Example (docx-templater)">
        ```typescript theme={null}
        const { generatePdf } = dsl.$modules;
        const { meta } = dsl.$variables;

        // fillData can be a Record<string, unknown> with docx-templater
        const fillData = {
          candidateName: 'John Doe',
          experience: '5 years',
          skills: ['TypeScript', 'React', 'Node.js'],
        };

        const { generatedFileInfo } = await dsl.$modules.generatePdf({
          generatedFileName: `CV_John_Doe_SIA_en.pptx`,
          templateKey: 'superSiaTemplatePptx',
          traceId: meta.traceId,
          fillData,
          version: 'docx-templater'
        });
        ```
      </Tab>
    </Tabs>
  </Accordion>
</AccordionGroup>

### Example - *Order a meal*

Let's imagine a restaurant workflow where a client can order a meal.

The workflow has two groups: `cook` and `client`.
In the first action, the client will select a meal to order and submit the form.
The goal of the effect is to:

* store the selected meal in the trace's data,
* add the `'Order received'` status to the trace with a progress of `20%`,
* and update the next available actions for each group:
* the `cook` group should be able to do the `prepareOrder` action and `comment`,
* the `client` group should only be able to do the `comment` action.

```ts orderMealEffect theme={null}
async function orderMealEffect(dsl: RestaurantWorkflowContext<FormData>) {
// We destructure the DSL variables for readability
  const { state, formData } = dsl.$variables;
  const { data, nextActions } = state;

  // The chosen meal is retrieved from the form data and stored in the data
  data.meal = formData.meal;

  data.status = {
    status: 'Order received',
    progress: 0.2
  };

  // Now, we update the next actions
  nextActions = {
    cook: ['prepareOrder', 'comment'],
    client: ['comment']
  };
}

const effects = [execJs(orderMealEffect)] satisfies Statement[];
```
