Skip to content
Open
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
9 changes: 9 additions & 0 deletions docs/oauth-scopes.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ This page lists the specific OAuth scopes required in external app for each SDK
| `resume()` | `OR.Jobs` or `OR.Jobs.Write` |
| `restart()` | `OR.Jobs` |

## Functions

Coded functions are invoked through their HTTP endpoint, which requires the `OR.Default` scope (auto-granted to any registered external app, but it must appear explicitly in the app's scope string).

| Method | OAuth Scope |
|--------|-------------|
| `getAll()` | `OR.Default` |
| `invoke()` | `OR.Default`, `OR.Folders` or `OR.Folders.Read` |

## Attachments

| Method | OAuth Scope |
Expand Down
1 change: 1 addition & 0 deletions docs/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ console.log(`Total count: ${allAssets.totalCount}`);
| Entities | `getAllRecords()` | ✅ Yes |
| Entities | `queryRecordsById()` | ✅ Yes |
| Processes | `getAll()` | ✅ Yes |
| Functions | `getAll()` | ✅ Yes |
| ProcessInstances | `getAll()` | ❌ No |
| CaseInstances | `getAll()` | ❌ No |
| CaseInstances | `getActionTasks()` | ✅ Yes |
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ nav:
- Entities:
- api/interfaces/entity/index.md
- Choice Sets: api/interfaces/ChoiceSetServiceModel.md
- Functions: api/interfaces/FunctionServiceModel.md
- Governance: api/interfaces/GovernanceServiceModel.md
- Maestro:
- Processes: api/interfaces/MaestroProcessesServiceModel.md
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,16 @@
"types": "./dist/notifications/index.d.ts",
"default": "./dist/notifications/index.cjs"
}
},
"./functions": {
"import": {
"types": "./dist/functions/index.d.ts",
"default": "./dist/functions/index.mjs"
},
"require": {
"types": "./dist/functions/index.d.ts",
"default": "./dist/functions/index.cjs"
}
}
},
"files": [
Expand Down
5 changes: 5 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ const serviceEntries = [
name: 'notifications',
input: 'src/services/notification/index.ts',
output: 'notifications/index'
},
{
name: 'functions',
input: 'src/services/orchestrator/functions/index.ts',
output: 'functions/index'
}
];

Expand Down
5 changes: 4 additions & 1 deletion src/core/http/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ export class ApiClient {

const isFormData = options.body instanceof FormData;
const defaultHeaders = await this.getDefaultHeaders();
if (isFormData) {
// FormData sets its own boundary header; GET/HEAD carry no body, and some
// gateways (e.g. function HTTP triggers) reject them when a json
// Content-Type is present. Explicit options.headers still take precedence.
if (isFormData || method === 'GET' || method === 'HEAD') {
delete defaultHeaders['Content-Type'];
}

Expand Down
18 changes: 18 additions & 0 deletions src/models/orchestrator/functions.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* OData query rewrite map for Functions (API field → SDK field), reversed by
* `transformOptions()` so callers can use SDK field names in `filter`,
* `orderby`, `select`, and `expand`.
*
* Responses are reshaped by `toFunctionResponse()` instead, which flattens the
* nested `Release` entity and drops the job-runner fields a map cannot express.
* Semantic renames only — case conversion is handled by `pascalToCamelCaseKeys()`.
*/
export const FunctionMap = {
organizationUnitId: 'folderId',
organizationUnitFullyQualifiedName: 'folderName',
releaseKey: 'processKey',
// The package fields are flattened out of the nested `Release` entity, so the
// API names are navigation paths. Keeps `filter: "processName eq '...'"` working.
'Release/Name': 'processName',
'Release/Slug': 'processSlug',
} as const;
48 changes: 48 additions & 0 deletions src/models/orchestrator/functions.internal-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Internal types for the Functions service — raw API wire formats before
* transformation. Not exported through the public barrel.
*/

/**
* Raw HTTP trigger row from `GET /odata/HttpTriggers` after
* `pascalToCamelCaseKeys()`. Only the fields consumed by the service are
* declared; the API returns many more job-runner fields that the SDK drops.
*/
export interface RawFunctionTrigger {
/** Trigger identifier (GUID). */
id: string;
/** Trigger name — unique within a folder. */
name: string;
/** URL path segment within the package. */
slug: string;
/** HTTP verb, e.g. `Post`. */
method: string;
/** Description from the function definition. */
description?: string | null;
/** Whether the trigger is enabled. */
enabled: boolean;
/** Default input arguments as a JSON string. */
inputArguments?: string | null;
/** Source file path inside the package. */
entryPointPath?: string | null;
/** Key (GUID) of the release that owns the trigger. */
releaseKey: string;
/** Numeric ID of the folder the trigger lives in. */
organizationUnitId: number;
/** Fully qualified folder name. */
organizationUnitFullyQualifiedName?: string | null;
/** Release (process) that packages the function. */
release: {
name: string;
slug: string;
};
}

/**
* Raw folder entity from `GET /odata/Folders({id})` — PascalCase wire format,
* limited to the field the Functions service consumes.
*/
export interface RawFolderResponse {
/** Folder key (GUID) — the `t/{key}` segment of the function invoke URL. */
Key: string;
}
188 changes: 188 additions & 0 deletions src/models/orchestrator/functions.models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { FunctionGetAllOptions, FunctionInvokeOptions, RawFunctionGetResponse } from './functions.types';
import { PaginatedResponse, NonPaginatedResponse, HasPaginationOptions } from '../../utils/pagination';

/** Combined response type for function data with bound methods. */
export type FunctionGetResponse = RawFunctionGetResponse & FunctionMethods;

/**
* Service for invoking UiPath Coded Functions.
*
* Coded functions are lightweight units of TypeScript/JavaScript or Python code
* deployed to UiPath and executed on demand. Each function is packaged as a
* process, exposed through an HTTP endpoint, and uniquely named within its folder.
* This service lets you discover deployed functions and invoke them with typed
* input and output — without dealing with the underlying process and trigger plumbing.
*
* ### Usage
*
* Prerequisites: Initialize the SDK first - see [Getting Started](/uipath-typescript/getting-started/#import-initialize)
*
* ```typescript
* import { Functions } from '@uipath/uipath-typescript/functions';
*
* const functions = new Functions(sdk);
* const result = await functions.invoke('my-function', { amount: 42 }, { folderId: <folderId> });
* ```
*/
export interface FunctionServiceModel {
/**
* Gets all functions in a folder with optional filtering and pagination.
*
* Returns each function's identity (name, slug, HTTP method), state, and
* packaging details, with an {@link FunctionMethods.invoke | invoke} method
* bound to each item. Folder context is required — pass one of `folderId`,
* `folderKey`, or `folderPath` in the options, or initialize the SDK with a
* folder context.
*
* One package can expose several functions, so filter on `processName`,
* `processSlug`, or `processKey` to narrow the result to a single package.
*
* @param options - Query options including folder scoping (`folderId` / `folderKey` / `folderPath`), filtering, and pagination options
* @returns Promise resolving to either an array of functions {@link NonPaginatedResponse}<{@link FunctionGetResponse}> or a {@link PaginatedResponse}<{@link FunctionGetResponse}> when pagination options are used.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orphaned {@link} tag — this line renders as stray text (or a dangling link) in TypeDoc output. It appears to be a copy-paste artifact; the @returns description on the line above already includes the two relevant links. Remove this line.

Suggested change
* @returns Promise resolving to either an array of functions {@link NonPaginatedResponse}<{@link FunctionGetResponse}> or a {@link PaginatedResponse}<{@link FunctionGetResponse}> when pagination options are used.

* @example
* ```typescript
* // Get all functions in a folder
* const deployed = await functions.getAll({ folderId: <folderId> });
*
* // By folder path
* const shared = await functions.getAll({ folderPath: 'Shared/Finance' });
*
* // With filtering
* const enabled = await functions.getAll({
* folderId: <folderId>,
* filter: 'enabled eq true',
* orderby: 'name asc',
* });
*
* // Only the functions deployed from one package
* const fromPackage = await functions.getAll({
* folderId: <folderId>,
* filter: "processName eq 'my-functions'",
* });
*
* // First page with pagination
* const page1 = await functions.getAll({ folderId: <folderId>, pageSize: 10 });
*
* // Navigate using cursor
* if (page1.hasNextPage) {
* const page2 = await functions.getAll({ folderId: <folderId>, cursor: page1.nextCursor });
* }
* ```
*/
getAll<T extends FunctionGetAllOptions = FunctionGetAllOptions>(
options?: T
): Promise<
T extends HasPaginationOptions<T>
? PaginatedResponse<FunctionGetResponse>
: NonPaginatedResponse<FunctionGetResponse>
>;

/**
* Invokes a function by name and returns its output.
*
* The call is synchronous — it resolves with the function's output. Coded
* functions exposed over HTTP are request/response only, so a run that exceeds
* the platform's execution limit fails rather than continuing in the background.
*
* Type the input and output by supplying the generics — they should match the
* input/output schema the function declares. Folder context is required — pass
* one of `folderId`, `folderKey`, or `folderPath` in the options, or initialize
* the SDK with a folder context.
*
* @param name - Name of the function to invoke (unique within a folder)
* @param input - Input for the function, sent as the request body (or as query
* parameters for functions declared with the `Get` method). Defaults to an empty object.
* @param options - Folder scoping (`folderId` / `folderKey` / `folderPath`) and
* parent job attribution (`jobKey`)
* @returns Promise resolving to the function's output
*
* @example
* ```typescript
* // Invoke a function
* const result = await functions.invoke('hello', { name: 'Alice' }, { folderId: <folderId> });
* ```
*
* @example
* ```typescript
* // Typed input and output, attributed to a parent job's licensing transaction
* interface SyncInput { since: string }
* interface SyncOutput { processed: number }
*
* const result = await functions.invoke<SyncInput, SyncOutput>(
* 'sync-invoices',
* { since: '2026-01-01' },
* { folderPath: 'Shared/Finance', jobKey: '<parentJobKey>' }
* );
* console.log(result.processed);
* ```
*/
invoke<TInput extends object = Record<string, unknown>, TOutput = unknown>(
name: string,
input?: TInput,
options?: FunctionInvokeOptions
): Promise<TOutput>;
}

/**
* Methods available on function response objects.
* These are bound to the function data and delegate to the service.
*/
export interface FunctionMethods {
/**
* Invokes this function and returns its output.
*
* @param input - Input for the function, sent as the request body (or as query
* parameters for functions declared with the `Get` method). Defaults to an empty object.
* @returns Promise resolving to the function's output
*
* @example
* ```typescript
* const deployed = await functions.getAll({ folderId: <folderId> });
* const hello = deployed.items.find(f => f.name === 'hello');
*
* if (hello) {
* const result = await hello.invoke({ name: 'Alice' });
* }
* ```
*/
invoke<TInput extends object = Record<string, unknown>, TOutput = unknown>(
input?: TInput
): Promise<TOutput>;
}

/**
* Creates methods for a function response object.
*
* @param functionData - The transformed function data
* @param service - The function service instance
* @returns Object containing function methods
*/
function createFunctionMethods(
functionData: RawFunctionGetResponse,
service: FunctionServiceModel
): FunctionMethods {
return {
async invoke<TInput extends object = Record<string, unknown>, TOutput = unknown>(
input?: TInput
): Promise<TOutput> {
if (!functionData.name) throw new Error('Function name is undefined');
if (!functionData.folderId) throw new Error('Function folderId is undefined');
return service.invoke<TInput, TOutput>(functionData.name, input, { folderId: functionData.folderId });
},
};
}

/**
* Creates a function response with bound methods.
*
* @param functionData - The transformed function data
* @param service - The function service instance
* @returns A function object with added methods
*/
export function createFunctionWithMethods(
functionData: RawFunctionGetResponse,
service: FunctionServiceModel
): FunctionGetResponse {
const methods = createFunctionMethods(functionData, service);
return Object.assign({}, functionData, methods);
}
Loading
Loading