diff --git a/README.md b/README.md index 0fe7e02..3e2621d 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ const make = new Make('your-api-key', 'eu2.make.com', { - **SDK Apps** - Create and manage custom Make applications - **SDK Modules** - Building blocks for custom apps +- **SDK Endpoints** - HTTP endpoints exposed by custom apps - **SDK Connections** - Authentication for custom apps - **SDK Functions** - Reusable code blocks within custom apps - **SDK RPCs** - Remote procedure calls for custom apps @@ -212,12 +213,13 @@ All tools are organized into the following categories: - `teams` - `public-templates` - `users` -- `sdk.apps` -- `sdk.connections` -- `sdk.functions` -- `sdk.modules` -- `sdk.rpcs` -- `sdk.webhooks` +- `sdk-apps` +- `sdk-connections` +- `sdk-endpoints` +- `sdk-functions` +- `sdk-modules` +- `sdk-rpcs` +- `sdk-webhooks` ### Tool Scopes diff --git a/package-lock.json b/package-lock.json index 9114e4b..e6ac81c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@makehq/sdk", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@makehq/sdk", - "version": "1.5.0", + "version": "1.6.0", "license": "MIT", "devDependencies": { "@eslint/js": "^9.22.0", diff --git a/package.json b/package.json index e38018c..ddf9594 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@makehq/sdk", - "version": "1.5.0", + "version": "1.6.0", "description": "Make TypeScript SDK", "license": "MIT", "author": "Make", diff --git a/src/endpoints/sdk/endpoints.tools.ts b/src/endpoints/sdk/endpoints.tools.ts new file mode 100644 index 0000000..9ea17fd --- /dev/null +++ b/src/endpoints/sdk/endpoints.tools.ts @@ -0,0 +1,330 @@ +import type { Make } from '../../make.js'; +import type { MakeTool } from '../../tools.js'; +import type { SDKEndpointAnnotations, SDKEndpointSectionType } from './endpoints.js'; + +const SECTION_ENUM = ['api', 'scope', 'inputParameters', 'outputParameters']; + +export const tools: MakeTool[] = [ + { + name: 'sdk-endpoints_list', + title: 'List SDK Endpoints', + description: 'List all Endpoints for the app.', + category: 'sdk-endpoints', + scope: 'sdk-apps:read', + scopeId: undefined, + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + }, + required: ['appName', 'appVersion'], + }, + examples: [{ appName: 'my-app', appVersion: 1 }], + execute: async (make: Make, args: { appName: string; appVersion: number }) => { + return await make.sdk.endpoints.list(args.appName, args.appVersion); + }, + }, + { + name: 'sdk-endpoints_get', + title: 'Get SDK Endpoint', + description: 'Get a single Endpoint by name.', + category: 'sdk-endpoints', + scope: 'sdk-apps:read', + scopeId: undefined, + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + endpointName: { type: 'string', description: 'The name of the Endpoint' }, + }, + required: ['appName', 'appVersion', 'endpointName'], + }, + examples: [{ appName: 'my-app', appVersion: 1, endpointName: 'getEntity' }], + execute: async (make: Make, args: { appName: string; appVersion: number; endpointName: string }) => { + return await make.sdk.endpoints.get(args.appName, args.appVersion, args.endpointName); + }, + }, + { + name: 'sdk-endpoints_create', + title: 'Create SDK Endpoint', + description: 'Create a new Endpoint.', + category: 'sdk-endpoints', + scope: 'sdk-apps:write', + scopeId: undefined, + annotations: { + idempotentHint: true, + destructiveHint: false, + }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + name: { type: 'string', description: 'The name of the Endpoint' }, + label: { type: 'string', description: 'The label of the Endpoint' }, + description: { type: 'string', description: 'The description of the Endpoint' }, + attachedAccounts: { + type: 'array', + items: { type: 'string' }, + description: 'Connection names to attach to the Endpoint', + }, + }, + required: ['appName', 'appVersion', 'name', 'label'], + }, + examples: [{ appName: 'my-app', appVersion: 1, name: 'getEntity', label: 'Get Entity' }], + execute: async ( + make: Make, + args: { + appName: string; + appVersion: number; + name: string; + label: string; + description?: string; + attachedAccounts?: string[]; + }, + ) => { + const { appName, appVersion, ...body } = args; + return await make.sdk.endpoints.create(appName, appVersion, body); + }, + }, + { + name: 'sdk-endpoints_update', + title: 'Update SDK Endpoint', + description: "Update an existing Endpoint's metadata.", + category: 'sdk-endpoints', + scope: 'sdk-apps:write', + scopeId: undefined, + annotations: { + idempotentHint: true, + destructiveHint: false, + }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + endpointName: { type: 'string', description: 'The name of the Endpoint' }, + label: { type: 'string', description: 'The label of the Endpoint' }, + description: { type: 'string', description: 'The description of the Endpoint' }, + context: { type: 'string', description: 'Context for how to use the Endpoint' }, + annotations: { + type: 'object', + description: 'MCP-inspired behavior hints', + properties: { + readOnlyHint: { + type: 'boolean', + description: 'If true, the Endpoint does not modify its environment.', + }, + destructiveHint: { + type: 'boolean', + description: 'If true, the Endpoint may perform destructive updates.', + }, + idempotentHint: { + type: 'boolean', + description: 'If true, repeated calls with the same arguments have no additional effect.', + }, + openWorldHint: { + type: 'boolean', + description: + 'If true, the Endpoint may interact with an "open world" of external entities.', + }, + }, + }, + attachedAccounts: { + type: 'array', + items: { type: 'string' }, + description: 'Connection names attached to the Endpoint (replaces the existing list)', + }, + }, + required: ['appName', 'appVersion', 'endpointName'], + }, + examples: [{ appName: 'my-app', appVersion: 1, endpointName: 'getEntity', label: 'Get Entity (Updated)' }], + execute: async ( + make: Make, + args: { + appName: string; + appVersion: number; + endpointName: string; + label?: string; + description?: string; + context?: string; + annotations?: SDKEndpointAnnotations; + attachedAccounts?: string[]; + }, + ) => { + const { appName, appVersion, endpointName, ...body } = args; + return await make.sdk.endpoints.update(appName, appVersion, endpointName, body); + }, + }, + { + name: 'sdk-endpoints_delete', + title: 'Delete SDK Endpoint', + description: 'Delete an Endpoint.', + category: 'sdk-endpoints', + scope: 'sdk-apps:write', + scopeId: undefined, + annotations: { + destructiveHint: true, + }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + endpointName: { type: 'string', description: 'The name of the Endpoint' }, + }, + required: ['appName', 'appVersion', 'endpointName'], + }, + examples: [{ appName: 'my-app', appVersion: 1, endpointName: 'getEntity' }], + execute: async (make: Make, args: { appName: string; appVersion: number; endpointName: string }) => { + await make.sdk.endpoints.delete(args.appName, args.appVersion, args.endpointName); + return `Endpoint has been deleted.`; + }, + }, + { + name: 'sdk-endpoints_set-public', + title: 'Set SDK Endpoint public', + description: 'Mark an SDK app Endpoint as public.', + category: 'sdk-endpoints', + scope: 'sdk-apps:write', + scopeId: undefined, + annotations: { idempotentHint: true, destructiveHint: false }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + endpointName: { type: 'string', description: 'The name of the Endpoint' }, + }, + required: ['appName', 'appVersion', 'endpointName'], + }, + examples: [{ appName: 'my-app', appVersion: 1, endpointName: 'getEntity' }], + execute: async (make: Make, args: { appName: string; appVersion: number; endpointName: string }) => { + await make.sdk.endpoints.makePublic(args.appName, args.appVersion, args.endpointName); + return `Endpoint has been made public.`; + }, + }, + { + name: 'sdk-endpoints_set-private', + title: 'Set SDK Endpoint private', + description: 'Mark an SDK app Endpoint as private.', + category: 'sdk-endpoints', + scope: 'sdk-apps:write', + scopeId: undefined, + annotations: { idempotentHint: true, destructiveHint: false }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + endpointName: { type: 'string', description: 'The name of the Endpoint' }, + }, + required: ['appName', 'appVersion', 'endpointName'], + }, + examples: [{ appName: 'my-app', appVersion: 1, endpointName: 'getEntity' }], + execute: async (make: Make, args: { appName: string; appVersion: number; endpointName: string }) => { + await make.sdk.endpoints.makePrivate(args.appName, args.appVersion, args.endpointName); + return `Endpoint has been made private.`; + }, + }, + { + name: 'sdk-endpoints_get-section', + title: 'Get SDK Endpoint section', + description: 'Get Endpoint section data.', + category: 'sdk-endpoints', + scope: 'sdk-apps:read', + scopeId: undefined, + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + endpointName: { type: 'string', description: 'The name of the Endpoint' }, + section: { + type: 'string', + enum: SECTION_ENUM, + description: 'The section to get', + }, + }, + required: ['appName', 'appVersion', 'endpointName', 'section'], + }, + examples: [{ appName: 'my-app', appVersion: 1, endpointName: 'getEntity', section: 'api' }], + execute: async ( + make: Make, + args: { + appName: string; + appVersion: number; + endpointName: string; + section: SDKEndpointSectionType; + }, + ) => { + return await make.sdk.endpoints.getSection(args.appName, args.appVersion, args.endpointName, args.section); + }, + }, + { + name: 'sdk-endpoints_set-section', + title: 'Set SDK Endpoint section', + description: 'Set Endpoint section data.', + category: 'sdk-endpoints', + scope: 'sdk-apps:write', + scopeId: undefined, + annotations: { + idempotentHint: true, + destructiveHint: false, + }, + inputSchema: { + type: 'object', + properties: { + appName: { type: 'string', description: 'The name of the app' }, + appVersion: { type: 'number', description: 'The version of the app' }, + endpointName: { type: 'string', description: 'The name of the Endpoint' }, + section: { + type: 'string', + enum: SECTION_ENUM, + description: 'The section to set', + }, + body: { type: 'string', description: 'The section data to set in JSONC format' }, + }, + required: ['appName', 'appVersion', 'endpointName', 'section', 'body'], + }, + examples: [ + { + appName: 'my-app', + appVersion: 1, + endpointName: 'getEntity', + section: 'api', + body: '{"url": "/entities/{{parameters.id}}", "method": "GET"}', + }, + ], + execute: async ( + make: Make, + args: { + appName: string; + appVersion: number; + endpointName: string; + section: SDKEndpointSectionType; + body: string; + }, + ) => { + await make.sdk.endpoints.setSection( + args.appName, + args.appVersion, + args.endpointName, + args.section, + args.body, + ); + return `Section '${args.section}' has been set.`; + }, + }, +]; diff --git a/src/endpoints/sdk/endpoints.ts b/src/endpoints/sdk/endpoints.ts new file mode 100644 index 0000000..79d9118 --- /dev/null +++ b/src/endpoints/sdk/endpoints.ts @@ -0,0 +1,217 @@ +import type { FetchFunction } from '../../types.js'; +import { JSONStringifyIfNotString } from '../../utils.js'; + +/** + * MCP-inspired behavior hints for an Endpoint. Only these keys are persisted; unknown keys are stripped. + */ +export type SDKEndpointAnnotations = { + /** If true, the Endpoint does not modify its environment. */ + readOnlyHint?: boolean; + /** If true, the Endpoint may perform destructive updates; if false, updates are only additive. */ + destructiveHint?: boolean; + /** If true, repeated calls with the same arguments have no additional effect. */ + idempotentHint?: boolean; + /** If true, the Endpoint may interact with an "open world" of external entities. */ + openWorldHint?: boolean; +}; + +/** + * Endpoint + */ +export type SDKEndpoint = { + /** The name of the Endpoint */ + name: string; + /** The label of the Endpoint */ + label: string; + /** The description of the Endpoint */ + description: string | null; + /** Context for how to use the Endpoint */ + context: string | null; + /** MCP-inspired behavior hints */ + annotations: SDKEndpointAnnotations; + /** Connection names attached to the Endpoint */ + attachedAccounts: string[]; + /** Whether the Endpoint is public */ + public: boolean; + /** Whether the Endpoint is approved */ + approved: boolean; + /** Whether the Endpoint is deprecated */ + deprecated: boolean; + /** Whether the Endpoint is archived */ + archived: boolean; + /** The endpoint schema version */ + schemaVersion: number; + /** Creation timestamp */ + createdAt: string; + /** Last update timestamp */ + updatedAt: string; +}; + +/** + * Endpoint section data (api, scope, inputParameters, outputParameters) + */ +export type SDKEndpointSection = string; + +/** + * Endpoint section type + */ +export type SDKEndpointSectionType = 'api' | 'scope' | 'inputParameters' | 'outputParameters'; + +/** + * Body for creating a new Endpoint + */ +export type CreateSDKEndpointBody = { + /** The name of the Endpoint */ + name: string; + /** The label of the Endpoint */ + label: string; + /** The description of the Endpoint */ + description?: string; + /** Connection names to attach to the Endpoint */ + attachedAccounts?: string[]; +}; + +/** + * Body for updating an Endpoint's metadata + */ +export type UpdateSDKEndpointBody = { + /** The label of the Endpoint */ + label?: string; + /** The description of the Endpoint */ + description?: string; + /** Context for how to use the Endpoint */ + context?: string; + /** MCP-inspired behavior hints */ + annotations?: SDKEndpointAnnotations; + /** Connection names attached to the Endpoint (replaces the existing list) */ + attachedAccounts?: string[]; +}; + +/** + * Internal response types (not exported) + */ +type ListSDKEndpointsResponse = { + appEndpoints: SDKEndpoint[]; +}; + +type GetSDKEndpointResponse = { + appEndpoint: SDKEndpoint; +}; + +/** + * Class providing methods for working with App Endpoints + */ +export class SDKEndpoints { + readonly #fetch: FetchFunction; + + constructor(fetch: FetchFunction) { + this.#fetch = fetch; + } + + /** + * List all Endpoints for the app + */ + async list(appName: string, appVersion: number): Promise { + const response = await this.#fetch(`/sdk/apps/${appName}/${appVersion}/endpoints`); + return response.appEndpoints || []; + } + + /** + * Get a single Endpoint by name + */ + async get(appName: string, appVersion: number, endpointName: string): Promise { + const response = await this.#fetch( + `/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}`, + ); + return response.appEndpoint; + } + + /** + * Create a new Endpoint + */ + async create(appName: string, appVersion: number, body: CreateSDKEndpointBody): Promise { + const response = await this.#fetch(`/sdk/apps/${appName}/${appVersion}/endpoints`, { + method: 'POST', + body, + }); + return response.appEndpoint; + } + + /** + * Update an existing Endpoint's metadata + */ + async update( + appName: string, + appVersion: number, + endpointName: string, + body: UpdateSDKEndpointBody, + ): Promise { + const response = await this.#fetch( + `/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}`, + { + method: 'PATCH', + body, + }, + ); + return response.appEndpoint; + } + + /** + * Delete an Endpoint + */ + async delete(appName: string, appVersion: number, endpointName: string): Promise { + await this.#fetch(`/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}`, { + method: 'DELETE', + }); + } + + /** + * Get a specific section of an Endpoint + * Available sections: api, scope, inputParameters, outputParameters + */ + async getSection( + appName: string, + appVersion: number, + endpointName: string, + section: SDKEndpointSectionType, + ): Promise { + const response = await this.#fetch( + `/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}/${section}`, + ); + return response; + } + + /** + * Set/update a specific section of an Endpoint + * Available sections: api, scope, inputParameters, outputParameters + */ + async setSection( + appName: string, + appVersion: number, + endpointName: string, + section: SDKEndpointSectionType, + body: SDKEndpointSection, + ): Promise { + await this.#fetch(`/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}/${section}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/jsonc', + }, + body: JSONStringifyIfNotString(body), + }); + } + + /** + * Make an Endpoint public. + */ + async makePublic(appName: string, appVersion: number, endpointName: string): Promise { + await this.#fetch(`/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}/public`, { method: 'POST' }); + } + + /** + * Make an Endpoint private. + */ + async makePrivate(appName: string, appVersion: number, endpointName: string): Promise { + await this.#fetch(`/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}/private`, { method: 'POST' }); + } +} diff --git a/src/index.ts b/src/index.ts index 0a98b72..b414cec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -182,6 +182,15 @@ export type { CreateSDKWebhookBody, UpdateSDKWebhookBody, } from './endpoints/sdk/webhooks.js'; +export type { + SDKEndpoint, + SDKEndpoints, + SDKEndpointAnnotations, + SDKEndpointSection, + SDKEndpointSectionType, + CreateSDKEndpointBody, + UpdateSDKEndpointBody, +} from './endpoints/sdk/endpoints.js'; export type { Team, Teams, CreateTeamBody, ListTeamsOptions, GetTeamOptions } from './endpoints/teams.js'; export type { PublicTemplate, diff --git a/src/make.ts b/src/make.ts index f597fc8..1920184 100644 --- a/src/make.ts +++ b/src/make.ts @@ -24,6 +24,7 @@ import { SDKConnections } from './endpoints/sdk/connections.js'; import { SDKFunctions } from './endpoints/sdk/functions.js'; import { SDKRPCs } from './endpoints/sdk/rpcs.js'; import { SDKWebhooks } from './endpoints/sdk/webhooks.js'; +import { SDKEndpoints } from './endpoints/sdk/endpoints.js'; import { buildUrl, createMakeError, isAPIKey, MakeError } from './utils.js'; import type { FetchOptions, JSONValue, QueryValue, RetryOptions } from './types.js'; import { VERSION } from './version.js'; @@ -218,6 +219,11 @@ export class Make { * Webhooks are used to listen for external events in apps */ readonly webhooks: SDKWebhooks; + /** + * Access to Endpoint-related endpoints + * Endpoints expose app functionality to AI agents + */ + readonly endpoints: SDKEndpoints; }; /** @@ -279,6 +285,7 @@ export class Make { functions: new SDKFunctions(this.fetch.bind(this)), rpcs: new SDKRPCs(this.fetch.bind(this)), webhooks: new SDKWebhooks(this.fetch.bind(this)), + endpoints: new SDKEndpoints(this.fetch.bind(this)), }; } diff --git a/src/tools.ts b/src/tools.ts index d247d3a..e2333f7 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -7,6 +7,7 @@ import { tools as SDKFunctionsTools } from './endpoints/sdk/functions.tools.js'; import { tools as SDKModulesTools } from './endpoints/sdk/modules.tools.js'; import { tools as SDKRPCsTools } from './endpoints/sdk/rpcs.tools.js'; import { tools as SDKWebhooksTools } from './endpoints/sdk/webhooks.tools.js'; +import { tools as SDKEndpointsTools } from './endpoints/sdk/endpoints.tools.js'; import { tools as ScenariosTools } from './endpoints/scenarios.tools.js'; import { tools as ConnectionsTools } from './endpoints/connections.tools.js'; @@ -198,6 +199,7 @@ export const MakeTools = [ ...SDKModulesTools, ...SDKRPCsTools, ...SDKWebhooksTools, + ...SDKEndpointsTools, // Core Endpoint Tools ...ScenariosTools, diff --git a/test/mocks/sdk/endpoints/create.json b/test/mocks/sdk/endpoints/create.json new file mode 100644 index 0000000..a8f61eb --- /dev/null +++ b/test/mocks/sdk/endpoints/create.json @@ -0,0 +1,17 @@ +{ + "appEndpoint": { + "name": "getEntity", + "label": "Get Entity", + "description": "Retrieves the given entity.", + "context": null, + "annotations": {}, + "attachedAccounts": [], + "public": false, + "approved": false, + "deprecated": false, + "archived": false, + "schemaVersion": 1, + "createdAt": "2026-06-26T00:00:00.000Z", + "updatedAt": "2026-06-26T00:00:00.000Z" + } +} diff --git a/test/mocks/sdk/endpoints/get.json b/test/mocks/sdk/endpoints/get.json new file mode 100644 index 0000000..3dad1a3 --- /dev/null +++ b/test/mocks/sdk/endpoints/get.json @@ -0,0 +1,17 @@ +{ + "appEndpoint": { + "name": "getEntity", + "label": "Get Entity", + "description": "Retrieves the given entity.", + "context": null, + "annotations": { "readOnlyHint": true }, + "attachedAccounts": [], + "public": false, + "approved": false, + "deprecated": false, + "archived": false, + "schemaVersion": 1, + "createdAt": "2026-06-26T00:00:00.000Z", + "updatedAt": "2026-06-26T00:00:00.000Z" + } +} diff --git a/test/mocks/sdk/endpoints/list.json b/test/mocks/sdk/endpoints/list.json new file mode 100644 index 0000000..a0b83ce --- /dev/null +++ b/test/mocks/sdk/endpoints/list.json @@ -0,0 +1,19 @@ +{ + "appEndpoints": [ + { + "name": "getEntity", + "label": "Get Entity", + "description": "Retrieves the given entity.", + "context": null, + "annotations": { "readOnlyHint": true }, + "attachedAccounts": [], + "public": false, + "approved": false, + "deprecated": false, + "archived": false, + "schemaVersion": 1, + "createdAt": "2026-06-26T00:00:00.000Z", + "updatedAt": "2026-06-26T00:00:00.000Z" + } + ] +} diff --git a/test/mocks/sdk/endpoints/update.json b/test/mocks/sdk/endpoints/update.json new file mode 100644 index 0000000..bdc4b2a --- /dev/null +++ b/test/mocks/sdk/endpoints/update.json @@ -0,0 +1,17 @@ +{ + "appEndpoint": { + "name": "getEntity", + "label": "Get Entity (Updated)", + "description": "Retrieves the given entity by its id.", + "context": "Use to fetch a single entity by its id.", + "annotations": { "readOnlyHint": true, "idempotentHint": true }, + "attachedAccounts": ["my-connection"], + "public": false, + "approved": false, + "deprecated": false, + "archived": false, + "schemaVersion": 1, + "createdAt": "2026-06-26T00:00:00.000Z", + "updatedAt": "2026-06-26T01:00:00.000Z" + } +} diff --git a/test/sdk/endpoints.integration.test.ts b/test/sdk/endpoints.integration.test.ts new file mode 100644 index 0000000..8a536fe --- /dev/null +++ b/test/sdk/endpoints.integration.test.ts @@ -0,0 +1,111 @@ +import 'dotenv/config'; +import { describe, expect, it } from '@jest/globals'; +import { Make } from '../../src/make.js'; + +const MAKE_API_KEY = String(process.env.MAKE_API_KEY || ''); +const MAKE_ZONE = String(process.env.MAKE_ZONE || ''); +const MAKE_APP_NAME = String(process.env.MAKE_APP_NAME || ''); + +const itif = (condition: boolean) => (condition ? it : it.skip); + +describe('Integration: SDK > Endpoints', () => { + const make = new Make(MAKE_API_KEY, MAKE_ZONE); + + let appName: string = MAKE_APP_NAME; + let appVersion: number = 1; + let endpointName: string; + + itif(!MAKE_APP_NAME)('Should create a temporary app for testing', async () => { + const app = await make.sdk.apps.create({ + name: `test-endpoints-${Date.now()}`, + label: `Test Endpoints App ${Date.now()}`, + description: 'This is a test app created for endpoint integration tests', + theme: '#FF5733', + language: 'en', + countries: ['us'], + private: true, + audience: 'countries', + }); + + expect(app).toBeDefined(); + expect(app.name).toBeDefined(); + expect(app.version).toBeDefined(); + appName = app.name; + appVersion = app.version; + }); + + it('Should create an endpoint', async () => { + const endpoint = await make.sdk.endpoints.create(appName, appVersion, { + name: `testEndpoint${Date.now()}`, + label: 'Test Endpoint', + description: 'This is a test endpoint created by integration tests', + }); + + expect(endpoint).toBeDefined(); + expect(endpoint.name).toBeDefined(); + expect(endpoint.label).toBe('Test Endpoint'); + endpointName = endpoint.name; + }); + + it('Should get the created endpoint', async () => { + const endpoint = await make.sdk.endpoints.get(appName, appVersion, endpointName); + expect(endpoint.name).toBe(endpointName); + expect(endpoint.label).toBe('Test Endpoint'); + }); + + it('Should list endpoints', async () => { + const endpoints = await make.sdk.endpoints.list(appName, appVersion); + expect(Array.isArray(endpoints)).toBe(true); + expect(endpoints.some(endpoint => endpoint.name === endpointName)).toBe(true); + }); + + it('Should set the endpoint api section', async () => { + const sectionData = JSON.stringify({ + url: '/entities/{{parameters.id}}', + method: 'GET', + response: { output: '{{body}}' }, + }); + + await make.sdk.endpoints.setSection(appName, appVersion, endpointName, 'api', sectionData); + }); + + it('Should get the endpoint api section', async () => { + const result = await make.sdk.endpoints.getSection(appName, appVersion, endpointName, 'api'); + expect(result).toBeDefined(); + }); + + it('Should update the endpoint', async () => { + const updatedEndpoint = await make.sdk.endpoints.update(appName, appVersion, endpointName, { + label: 'Updated Test Endpoint', + description: 'This is an updated test endpoint', + }); + + expect(updatedEndpoint.label).toBe('Updated Test Endpoint'); + }); + + it('Should make the endpoint public and private', async () => { + await expect(make.sdk.endpoints.makePublic(appName, appVersion, endpointName)).resolves.toBeUndefined(); + await expect(make.sdk.endpoints.makePrivate(appName, appVersion, endpointName)).resolves.toBeUndefined(); + }); + + it('Should delete the endpoint', async () => { + await make.sdk.endpoints.delete(appName, appVersion, endpointName); + + // Verify the endpoint is deleted by checking it's not in the list + const endpoints = await make.sdk.endpoints.list(appName, appVersion); + expect(endpoints.some(endpoint => endpoint.name === endpointName)).toBe(false); + }); + + itif(!MAKE_APP_NAME)('Should delete the temporary app', async () => { + await make.sdk.apps.delete(appName, appVersion); + + // Verify the app is deleted by expecting an error when trying to get it + try { + await make.sdk.apps.get(appName, appVersion); + // If we get here, the test should fail because the app should be deleted + expect(true).toBe(false); + } catch (error) { + expect(error).toBeDefined(); + } + }); +}); diff --git a/test/sdk/endpoints.spec.ts b/test/sdk/endpoints.spec.ts new file mode 100644 index 0000000..5d585ea --- /dev/null +++ b/test/sdk/endpoints.spec.ts @@ -0,0 +1,126 @@ +import { describe, expect, it } from '@jest/globals'; +import { Make } from '../../src/make.js'; +import { mockFetch } from '../test.utils.js'; + +import * as listMock from '../mocks/sdk/endpoints/list.json'; +import * as getMock from '../mocks/sdk/endpoints/get.json'; +import * as createMock from '../mocks/sdk/endpoints/create.json'; +import * as updateMock from '../mocks/sdk/endpoints/update.json'; + +const MAKE_API_KEY = 'api-key'; +const MAKE_ZONE = 'make.local'; + +describe('Endpoints: SDK > Endpoints', () => { + const make = new Make(MAKE_API_KEY, MAKE_ZONE); + const appName = 'test-app'; + const appVersion = 1; + const endpointName = 'getEntity'; + + it('Should list SDK app endpoints', async () => { + mockFetch(`GET https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/endpoints`, listMock); + + const result = await make.sdk.endpoints.list(appName, appVersion); + expect(result).toStrictEqual(listMock.appEndpoints); + }); + + it('Should get SDK app endpoint by name', async () => { + mockFetch(`GET https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}`, getMock); + + const result = await make.sdk.endpoints.get(appName, appVersion, endpointName); + expect(result).toStrictEqual(getMock.appEndpoint); + }); + + it('Should create SDK app endpoint', async () => { + const body = { + name: 'getEntity', + label: 'Get Entity', + description: 'Retrieves the given entity.', + attachedAccounts: ['my-connection'], + }; + mockFetch(`POST https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/endpoints`, createMock, req => { + expect(req.body).toStrictEqual(body); + expect(req.headers.get('content-type')).toBe('application/json'); + }); + + const result = await make.sdk.endpoints.create(appName, appVersion, body); + expect(result).toStrictEqual(createMock.appEndpoint); + }); + + it('Should update SDK app endpoint', async () => { + const body = { + label: 'Get Entity (Updated)', + description: 'Retrieves the given entity by its id.', + context: 'Use to fetch a single entity by its id.', + annotations: { readOnlyHint: true, idempotentHint: true }, + attachedAccounts: ['my-connection'], + }; + mockFetch( + `PATCH https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}`, + updateMock, + req => { + expect(req.body).toStrictEqual(body); + expect(req.headers.get('content-type')).toBe('application/json'); + }, + ); + + const result = await make.sdk.endpoints.update(appName, appVersion, endpointName, body); + expect(result).toStrictEqual(updateMock.appEndpoint); + }); + + it('Should delete SDK app endpoint', async () => { + mockFetch(`DELETE https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}`, null); + + await make.sdk.endpoints.delete(appName, appVersion, endpointName); + }); + + it('Should make endpoint public and private', async () => { + mockFetch( + [ + `POST https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}/public`, + { changed: true }, + undefined, + ], + [ + `POST https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}/private`, + { changed: true }, + undefined, + ], + ); + + await expect(make.sdk.endpoints.makePublic(appName, appVersion, endpointName)).resolves.toBeUndefined(); + await expect(make.sdk.endpoints.makePrivate(appName, appVersion, endpointName)).resolves.toBeUndefined(); + }); + + it('Should get endpoint section', async () => { + const section = 'api'; + // The API returns sections as application/jsonc (comments allowed), so the SDK + // hands back the raw text unchanged rather than a parsed object. + const sectionBody = ['{', ' // Fetch a single entity', ' "url": "/entities/{{parameters.id}}",', ' "method": "GET"', '}'].join('\n'); + mockFetch( + `GET https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}/${section}`, + sectionBody, + ); + + const result = await make.sdk.endpoints.getSection(appName, appVersion, endpointName, section); + expect(result).toBe(sectionBody); + }); + + it('Should set endpoint section', async () => { + const section = 'api'; + const body = JSON.stringify({ + url: '/entities/{{parameters.id}}', + method: 'GET', + response: { output: '{{body}}' }, + }); + mockFetch( + `PUT https://make.local/api/v2/sdk/apps/${appName}/${appVersion}/endpoints/${endpointName}/${section}`, + null, + req => { + expect(req.body).toStrictEqual(body); + expect(req.headers.get('content-type')).toBe('application/jsonc'); + }, + ); + + await make.sdk.endpoints.setSection(appName, appVersion, endpointName, section, body); + }); +});