Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@makehq/sdk",
"version": "0.8.1",
"version": "0.9.0",
"description": "Make TypeScript SDK",
"license": "MIT",
"author": "Make",
Expand Down
22 changes: 22 additions & 0 deletions src/endpoints/executions.mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ export const tools = [
return await make.executions.list(scenarioId, options);
},
},
{
name: 'executions_get_detail',
title: 'Get execution detail',
description: 'Get detailed result of a specific execution',
category: 'executions',
scope: 'scenarios:read',
identifier: 'scenarioId',
annotations: {
readOnlyHint: true,
},
inputSchema: {
type: 'object',
properties: {
scenarioId: { type: 'number', description: 'The scenario ID the execution belongs to' },
executionId: { type: 'string', description: 'The execution ID to retrieve' },
},
required: ['scenarioId', 'executionId'],
},
execute: async (make: Make, args: { scenarioId: number; executionId: string }) => {
return await make.executions.getDetail(args.scenarioId, args.executionId);
},
},
{
name: 'executions_get',
title: 'Get execution',
Expand Down
50 changes: 48 additions & 2 deletions src/endpoints/executions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FetchFunction, Pagination } from '../types.js';
import type { FetchFunction, Pagination, JSONValue } from '../types.js';

/**
* Represents an execution of a Make scenario.
Expand All @@ -11,7 +11,7 @@ export type Execution = {
/** Internal Make ID of the execution */
imtId: string;
/** Status of the execution (0 = pending, 1 = successful, 2 = successful with warnings, 3 = failed) */
status: number;
status: 0 | 1 | 2 | 3;
/** Duration of the execution in milliseconds */
duration: number;
/** Number of operations consumed by the execution */
Expand All @@ -30,6 +30,37 @@ export type Execution = {
instant: boolean;
};

/**
* Detailed result of a scenario execution.
* Includes high-level status, optional outputs, and error metadata.
*/
export type ExecutionDetail = {
/**
* Status of the scenario execution: RUNNING, SUCCESS, WARNING, or ERROR
*/
status: 'RUNNING' | 'SUCCESS' | 'WARNING' | 'ERROR';
/**
* Outputs of the scenario execution (shape depends on scenario configuration)
*/
outputs?: Record<string, JSONValue>;
/**
* Error information when execution resulted in WARNING or ERROR
*/
error?: {
/** Name of the error */
name: string;
/** Description of the error */
message: string;
/** Module that caused the error */
causeModule?: {
/** Name of the module */
name: string;
/** Name of the app */
appName: string;
};
};
};

/**
* Options for listing executions.
*/
Expand Down Expand Up @@ -60,6 +91,11 @@ type GetExecutionResponse = {
scenarioLogs: Execution;
};

/**
* Response format for getting detailed execution result.
*/
type GetExecutionDetailResponse = ExecutionDetail;

/**
* Class providing methods for working with Make executions.
* Executions represent the running instances of scenarios and provide
Expand Down Expand Up @@ -121,6 +157,16 @@ export class Executions {
return (await this.#fetch<GetExecutionResponse>(`/scenarios/${scenarioId}/logs/${executionId}`)).scenarioLogs;
}

/**
* Get detailed result of a specific execution.
* @param scenarioId The scenario ID the execution belongs to
* @param executionId The unique ID of the execution to retrieve
* @returns Promise with the detailed execution result
*/
async getDetail(scenarioId: number, executionId: string): Promise<ExecutionDetail> {
return await this.#fetch<GetExecutionDetailResponse>(`/scenarios/${scenarioId}/executions/${executionId}`);
}

/**
* Get details of a specific execution for an incomplete execution.
* @param incompleteExecutionId The incomplete execution ID
Expand Down
2 changes: 2 additions & 0 deletions src/endpoints/scenarios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ type UpdateScenarioInterfaceResponse = {
export type RunScenarioResponse = {
/** ID of the execution that was created */
executionId: string;
/** Status of the scenario execution when run in responsive mode (1 = successful, 2 = successful with warnings, 3 = failed) */
status?: 1 | 2 | 3;
/** Output data from the scenario execution (when run in responsive mode) */
outputs: unknown;
};
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export type {
CloneDataStructureBody,
} from './endpoints/data-structures.js';
export type { Enums, Country, Region, Timezone } from './endpoints/enums.js';
export type { Execution, Executions, ListExecutionsOptions } from './endpoints/executions.js';
export type { Execution, ExecutionDetail, Executions, ListExecutionsOptions } from './endpoints/executions.js';
export type {
Folder,
Folders,
Expand Down
71 changes: 71 additions & 0 deletions test/executions.integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'dotenv/config';
import { describe, expect, it } from '@jest/globals';
import { Make } from '../src/make.js';
import type { MakeError } from '../src/utils.js';

import * as executionsBlueprintMock from './mocks/executions/blueprint.json';

const MAKE_API_KEY = String(process.env.MAKE_API_KEY || '');
const MAKE_ZONE = String(process.env.MAKE_ZONE || '');
const MAKE_TEAM = Number(process.env.MAKE_TEAM || 0);

describe('Integration: Executions', () => {
const make = new Make(MAKE_API_KEY, MAKE_ZONE);

let scenarioId: number;
let executionId: string;

it('Should create a scenario to run', async () => {
const scenario = await make.scenarios.create({
teamId: MAKE_TEAM,
scheduling: '{"type":"on-demand"}',
blueprint: JSON.stringify(executionsBlueprintMock),
});

expect(scenario).toBeDefined();
expect(scenario.id).toBeDefined();
scenarioId = scenario.id!;
});

it('Should activate a scenario to run', async () => {
const activated = await make.scenarios.activate(scenarioId);
expect(activated).toBeTruthy();
});

it('Should run the scenario', async () => {
const runResult = await make.scenarios.run(scenarioId, { name: 'John Doe' }, { responsive: true });
expect(runResult).toBeDefined();
expect(runResult.executionId).toBeDefined();
expect(runResult.status).toBe(1);
expect(runResult.outputs).toEqual({ greeting: 'Hello, John Doe!' });
executionId = runResult.executionId;
});

it('Should get execution detail', async () => {
let attempts = 5;
while (attempts-- > 0) {
try {
const detail = await make.executions.getDetail(scenarioId, executionId);
expect(detail).toBeDefined();
expect(detail.status).toBe('SUCCESS');
expect(detail.outputs).toEqual({ greeting: 'Hello, John Doe!' });
return;
} catch (err: unknown) {
if ((err as MakeError)?.statusCode !== 404) throw err;
await new Promise(resolve => setTimeout(resolve, 1000));
}
}

throw new Error('Failed to fetch execution detail after multiple retries');
});

it('Cleanup: delete the scenario', async () => {
await make.scenarios.delete(scenarioId);
try {
await make.scenarios.get(scenarioId);
expect(true).toBe(false);
} catch (error) {
expect(error).toBeDefined();
}
});
});
11 changes: 11 additions & 0 deletions test/executions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { mockFetch } from './test.utils.js';

import * as executionsListMock from './mocks/executions/list.json';
import * as executionsGetMock from './mocks/executions/get.json';
import * as executionsGetDetailMock from './mocks/executions/get-detail.json';

const MAKE_API_KEY = 'api-key';
const MAKE_ZONE = 'make.local';
Expand All @@ -29,6 +30,16 @@ describe('Endpoints: Executions', () => {
expect(result).toStrictEqual(executionsGetMock.scenarioLogs);
});

it('Should get execution detail', async () => {
mockFetch(
'GET https://make.local/api/v2/scenarios/123456/executions/cc1c49323b344687a324888762206003',
executionsGetDetailMock,
);

const result = await make.executions.getDetail(123456, 'cc1c49323b344687a324888762206003');
expect(result).toStrictEqual(executionsGetDetailMock);
});

it('Should list executions for incomplete execution', async () => {
mockFetch('GET https://make.local/api/v2/dlqs/123456/logs', executionsListMock);

Expand Down
62 changes: 62 additions & 0 deletions test/mocks/executions/blueprint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"name": "Simple scenario",
"flow": [
{
"id": 2,
"module": "scenario-service:ReturnData",
"version": 2,
"parameters": {},
"mapper": {
"greeting": "Hello, {{var.input.name}}!"
},
"metadata": {
"designer": {
"x": 0,
"y": 0
},
"restore": {},
"expect": [
{
"name": "greeting",
"type": "text"
}
]
}
}
],
"metadata": {
"instant": false,
"version": 1,
"scenario": {
"roundtrips": 1,
"maxErrors": 3,
"autoCommit": true,
"autoCommitTriggerLast": true,
"sequential": false,
"slots": null,
"confidential": false,
"dataloss": false,
"dlq": false,
"freshVariables": false
},
"designer": {
"orphans": []
},
"zone": "eu1.make.com",
"notes": []
},
"io": {
"input_spec": [
{
"name": "name",
"type": "text"
}
],
"output_spec": [
{
"name": "greeting",
"type": "text"
}
]
}
}
7 changes: 7 additions & 0 deletions test/mocks/executions/get-detail.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"status": "SUCCESS",
"outputs": {
"output1": "text output",
"output2": 123
}
}