Skip to content

Commit 5d5e6f8

Browse files
committed
add more tools
1 parent 5a3cda5 commit 5d5e6f8

30 files changed

+3223
-92
lines changed

apps/docs/content/docs/en/tools/cloudflare.mdx

Lines changed: 163 additions & 15 deletions
Large diffs are not rendered by default.

apps/docs/content/docs/en/tools/microsoft_dataverse.mdx

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ The Dataverse integration empowers solution builders and business users to autom
2727
Connect Microsoft Dataverse to your automations to unlock sophisticated data management, orchestration, and business logic across your apps, teams, and cloud services.
2828
{/* MANUAL-CONTENT-END */}
2929

30+
3031
## Usage Instructions
3132

32-
Integrate Microsoft Dataverse into your workflow. Create, read, update, delete, upsert, associate, and query records in Dataverse tables using the Web API. Works with Dynamics 365, Power Platform, and custom Dataverse environments.
33+
Integrate Microsoft Dataverse into your workflow. Create, read, update, delete, upsert, associate, query, search, and execute actions and functions against Dataverse tables using the Web API. Supports bulk operations, FetchXML, file uploads, and relevance search. Works with Dynamics 365, Power Platform, and custom Dataverse environments.
3334

3435

3536

@@ -62,6 +63,27 @@ Associate two records in Microsoft Dataverse via a navigation property. Creates
6263
| `targetEntitySetName` | string | Target entity set name used in the association |
6364
| `targetRecordId` | string | Target record GUID that was associated |
6465

66+
### `microsoft_dataverse_create_multiple`
67+
68+
Create multiple records of the same table type in a single request. Each record in the Targets array must include an @odata.type annotation. Recommended batch size: 100-1000 records for standard tables.
69+
70+
#### Input
71+
72+
| Parameter | Type | Required | Description |
73+
| --------- | ---- | -------- | ----------- |
74+
| `environmentUrl` | string | Yes | Dataverse environment URL \(e.g., https://myorg.crm.dynamics.com\) |
75+
| `entitySetName` | string | Yes | Entity set name \(plural table name, e.g., accounts, contacts\) |
76+
| `entityLogicalName` | string | Yes | Table logical name for @odata.type annotation \(e.g., account, contact\). Used to set Microsoft.Dynamics.CRM.\{entityLogicalName\} on each record. |
77+
| `records` | object | Yes | Array of record objects to create. Each record should contain column logical names as keys. The @odata.type annotation is added automatically. |
78+
79+
#### Output
80+
81+
| Parameter | Type | Description |
82+
| --------- | ---- | ----------- |
83+
| `ids` | array | Array of GUIDs for the created records |
84+
| `count` | number | Number of records created |
85+
| `success` | boolean | Whether all records were created successfully |
86+
6587
### `microsoft_dataverse_create_record`
6688

6789
Create a new record in a Microsoft Dataverse table. Requires the entity set name (plural table name) and record data as a JSON object.
@@ -125,6 +147,93 @@ Remove an association between two records in Microsoft Dataverse. For collection
125147
| `navigationProperty` | string | Navigation property used for the disassociation |
126148
| `targetRecordId` | string | Target record GUID that was disassociated |
127149

150+
### `microsoft_dataverse_download_file`
151+
152+
Download a file from a file or image column on a Dataverse record. Returns the file content as a base64-encoded string along with file metadata from response headers.
153+
154+
#### Input
155+
156+
| Parameter | Type | Required | Description |
157+
| --------- | ---- | -------- | ----------- |
158+
| `environmentUrl` | string | Yes | Dataverse environment URL \(e.g., https://myorg.crm.dynamics.com\) |
159+
| `entitySetName` | string | Yes | Entity set name \(plural table name, e.g., accounts, contacts\) |
160+
| `recordId` | string | Yes | Record GUID to download the file from |
161+
| `fileColumn` | string | Yes | File or image column logical name \(e.g., entityimage, cr_document\) |
162+
163+
#### Output
164+
165+
| Parameter | Type | Description |
166+
| --------- | ---- | ----------- |
167+
| `fileContent` | string | Base64-encoded file content |
168+
| `fileName` | string | Name of the downloaded file |
169+
| `fileSize` | number | File size in bytes |
170+
| `mimeType` | string | MIME type of the file |
171+
| `success` | boolean | Whether the file was downloaded successfully |
172+
173+
### `microsoft_dataverse_execute_action`
174+
175+
Execute a bound or unbound Dataverse action. Actions perform operations with side effects (e.g., Merge, GrantAccess, SendEmail, QualifyLead). For bound actions, provide the entity set name and record ID.
176+
177+
#### Input
178+
179+
| Parameter | Type | Required | Description |
180+
| --------- | ---- | -------- | ----------- |
181+
| `environmentUrl` | string | Yes | Dataverse environment URL \(e.g., https://myorg.crm.dynamics.com\) |
182+
| `actionName` | string | Yes | Action name \(e.g., Merge, GrantAccess, SendEmail\). Do not include the Microsoft.Dynamics.CRM. namespace prefix for unbound actions. |
183+
| `entitySetName` | string | No | Entity set name for bound actions \(e.g., accounts\). Leave empty for unbound actions. |
184+
| `recordId` | string | No | Record GUID for bound actions. Leave empty for unbound or collection-bound actions. |
185+
| `parameters` | object | No | Action parameters as a JSON object. For entity references, include @odata.type annotation \(e.g., \{"Target": \{"@odata.type": "Microsoft.Dynamics.CRM.account", "accountid": "..."\}\}\) |
186+
187+
#### Output
188+
189+
| Parameter | Type | Description |
190+
| --------- | ---- | ----------- |
191+
| `result` | object | Action response data. Structure varies by action. Null for actions that return 204 No Content. |
192+
| `success` | boolean | Whether the action executed successfully |
193+
194+
### `microsoft_dataverse_execute_function`
195+
196+
Execute a bound or unbound Dataverse function. Functions are read-only operations (e.g., RetrievePrincipalAccess, RetrieveTotalRecordCount, InitializeFrom). For bound functions, provide the entity set name and record ID.
197+
198+
#### Input
199+
200+
| Parameter | Type | Required | Description |
201+
| --------- | ---- | -------- | ----------- |
202+
| `environmentUrl` | string | Yes | Dataverse environment URL \(e.g., https://myorg.crm.dynamics.com\) |
203+
| `functionName` | string | Yes | Function name \(e.g., RetrievePrincipalAccess, RetrieveTotalRecordCount\). Do not include the Microsoft.Dynamics.CRM. namespace prefix for unbound functions. |
204+
| `entitySetName` | string | No | Entity set name for bound functions \(e.g., systemusers\). Leave empty for unbound functions. |
205+
| `recordId` | string | No | Record GUID for bound functions. Leave empty for unbound functions. |
206+
| `parameters` | string | No | Function parameters as a comma-separated list of name=value pairs for the URL \(e.g., "LocalizedStandardName=\'Pacific Standard Time\ |
207+
208+
#### Output
209+
210+
| Parameter | Type | Description |
211+
| --------- | ---- | ----------- |
212+
| `result` | object | Function response data. Structure varies by function. |
213+
| `success` | boolean | Whether the function executed successfully |
214+
215+
### `microsoft_dataverse_fetchxml_query`
216+
217+
Execute a FetchXML query against a Microsoft Dataverse table. FetchXML supports aggregation, grouping, linked-entity joins, and complex filtering beyond OData capabilities.
218+
219+
#### Input
220+
221+
| Parameter | Type | Required | Description |
222+
| --------- | ---- | -------- | ----------- |
223+
| `environmentUrl` | string | Yes | Dataverse environment URL \(e.g., https://myorg.crm.dynamics.com\) |
224+
| `entitySetName` | string | Yes | Entity set name \(plural table name, e.g., accounts, contacts\) |
225+
| `fetchXml` | string | Yes | FetchXML query string. Must include <fetch> root element and <entity> child element matching the table logical name. |
226+
227+
#### Output
228+
229+
| Parameter | Type | Description |
230+
| --------- | ---- | ----------- |
231+
| `records` | array | Array of Dataverse records. Each record has dynamic columns based on the table schema. |
232+
| `count` | number | Number of records returned in the current page |
233+
| `fetchXmlPagingCookie` | string | Paging cookie for retrieving the next page of results |
234+
| `moreRecords` | boolean | Whether more records are available beyond the current page |
235+
| `success` | boolean | Operation success status |
236+
128237
### `microsoft_dataverse_get_record`
129238

130239
Retrieve a single record from a Microsoft Dataverse table by its ID. Supports $select and $expand OData query options.
@@ -174,6 +283,60 @@ Query and list records from a Microsoft Dataverse table. Supports OData query op
174283
| `nextLink` | string | URL for the next page of results |
175284
| `success` | boolean | Operation success status |
176285

286+
### `microsoft_dataverse_search`
287+
288+
Perform a full-text relevance search across Microsoft Dataverse tables. Requires Dataverse Search to be enabled on the environment. Supports simple and Lucene query syntax.
289+
290+
#### Input
291+
292+
| Parameter | Type | Required | Description |
293+
| --------- | ---- | -------- | ----------- |
294+
| `environmentUrl` | string | Yes | Dataverse environment URL \(e.g., https://myorg.crm.dynamics.com\) |
295+
| `searchTerm` | string | Yes | Search text \(1-100 chars\). Supports simple syntax: + \(AND\), \| \(OR\), - \(NOT\), * \(wildcard\), "exact phrase" |
296+
| `entities` | string | No | JSON array of search entity configs. Each object: \{"Name":"account","SelectColumns":\["name"\],"SearchColumns":\["name"\],"Filter":"statecode eq 0"\} |
297+
| `filter` | string | No | Global OData filter applied across all entities \(e.g., "createdon gt 2024-01-01"\) |
298+
| `facets` | string | No | JSON array of facet specifications \(e.g., \["entityname,count:100","ownerid,count:100"\]\) |
299+
| `top` | number | No | Maximum number of results \(default: 50, max: 100\) |
300+
| `skip` | number | No | Number of results to skip for pagination |
301+
| `orderBy` | string | No | JSON array of sort expressions \(e.g., \["createdon desc"\]\) |
302+
| `searchMode` | string | No | Search mode: "any" \(default, match any term\) or "all" \(match all terms\) |
303+
| `searchType` | string | No | Query type: "simple" \(default\) or "lucene" \(enables regex, fuzzy, proximity, boosting\) |
304+
305+
#### Output
306+
307+
| Parameter | Type | Description |
308+
| --------- | ---- | ----------- |
309+
| `results` | array | Array of search result objects |
310+
|`Id` | string | Record GUID |
311+
|`EntityName` | string | Table logical name \(e.g., account, contact\) |
312+
|`ObjectTypeCode` | number | Entity type code |
313+
|`Attributes` | object | Record attributes matching the search. Keys are column logical names. |
314+
|`Highlights` | object | Highlighted search matches. Keys are column names, values are arrays of strings with \{crmhit\}/\{/crmhit\} markers. |
315+
|`Score` | number | Relevance score for this result |
316+
| `totalCount` | number | Total number of matching records across all tables |
317+
| `count` | number | Number of results returned in this page |
318+
| `facets` | object | Facet results when facets were requested. Keys are facet names, values are arrays of facet value objects with count and value properties. |
319+
| `success` | boolean | Operation success status |
320+
321+
### `microsoft_dataverse_update_multiple`
322+
323+
Update multiple records of the same table type in a single request. Each record must include its primary key. Only include columns that need to be changed. Recommended batch size: 100-1000 records.
324+
325+
#### Input
326+
327+
| Parameter | Type | Required | Description |
328+
| --------- | ---- | -------- | ----------- |
329+
| `environmentUrl` | string | Yes | Dataverse environment URL \(e.g., https://myorg.crm.dynamics.com\) |
330+
| `entitySetName` | string | Yes | Entity set name \(plural table name, e.g., accounts, contacts\) |
331+
| `entityLogicalName` | string | Yes | Table logical name for @odata.type annotation \(e.g., account, contact\). Used to set Microsoft.Dynamics.CRM.\{entityLogicalName\} on each record. |
332+
| `records` | object | Yes | Array of record objects to update. Each record must include its primary key \(e.g., accountid\) and only the columns being changed. The @odata.type annotation is added automatically. |
333+
334+
#### Output
335+
336+
| Parameter | Type | Description |
337+
| --------- | ---- | ----------- |
338+
| `success` | boolean | Whether all records were updated successfully |
339+
177340
### `microsoft_dataverse_update_record`
178341

179342
Update an existing record in a Microsoft Dataverse table. Only send the columns you want to change.
@@ -194,6 +357,31 @@ Update an existing record in a Microsoft Dataverse table. Only send the columns
194357
| `recordId` | string | The ID of the updated record |
195358
| `success` | boolean | Operation success status |
196359

360+
### `microsoft_dataverse_upload_file`
361+
362+
Upload a file to a file or image column on a Dataverse record. Supports single-request upload for files up to 128 MB. The file content must be provided as a base64-encoded string.
363+
364+
#### Input
365+
366+
| Parameter | Type | Required | Description |
367+
| --------- | ---- | -------- | ----------- |
368+
| `environmentUrl` | string | Yes | Dataverse environment URL \(e.g., https://myorg.crm.dynamics.com\) |
369+
| `entitySetName` | string | Yes | Entity set name \(plural table name, e.g., accounts, contacts\) |
370+
| `recordId` | string | Yes | Record GUID to upload the file to |
371+
| `fileColumn` | string | Yes | File or image column logical name \(e.g., entityimage, cr_document\) |
372+
| `fileName` | string | Yes | Name of the file being uploaded \(e.g., document.pdf\) |
373+
| `file` | file | No | File to upload \(UserFile object\) |
374+
| `fileContent` | string | No | Base64-encoded file content \(legacy\) |
375+
376+
#### Output
377+
378+
| Parameter | Type | Description |
379+
| --------- | ---- | ----------- |
380+
| `recordId` | string | Record GUID the file was uploaded to |
381+
| `fileColumn` | string | File column the file was uploaded to |
382+
| `fileName` | string | Name of the uploaded file |
383+
| `success` | boolean | Whether the file was uploaded successfully |
384+
197385
### `microsoft_dataverse_upsert_record`
198386

199387
Create or update a record in a Microsoft Dataverse table. If a record with the given ID exists, it is updated; otherwise, a new record is created.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { createLogger } from '@sim/logger'
2+
import { type NextRequest, NextResponse } from 'next/server'
3+
import { z } from 'zod'
4+
import { checkInternalAuth } from '@/lib/auth/hybrid'
5+
import { generateRequestId } from '@/lib/core/utils/request'
6+
import { RawFileInputSchema } from '@/lib/uploads/utils/file-schemas'
7+
import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils'
8+
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
9+
10+
export const dynamic = 'force-dynamic'
11+
12+
const logger = createLogger('DataverseUploadFileAPI')
13+
14+
const DataverseUploadFileSchema = z.object({
15+
accessToken: z.string().min(1, 'Access token is required'),
16+
environmentUrl: z.string().min(1, 'Environment URL is required'),
17+
entitySetName: z.string().min(1, 'Entity set name is required'),
18+
recordId: z.string().min(1, 'Record ID is required'),
19+
fileColumn: z.string().min(1, 'File column is required'),
20+
fileName: z.string().min(1, 'File name is required'),
21+
file: RawFileInputSchema.optional().nullable(),
22+
fileContent: z.string().optional().nullable(),
23+
})
24+
25+
export async function POST(request: NextRequest) {
26+
const requestId = generateRequestId()
27+
28+
try {
29+
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
30+
31+
if (!authResult.success) {
32+
logger.warn(`[${requestId}] Unauthorized Dataverse upload attempt: ${authResult.error}`)
33+
return NextResponse.json(
34+
{ success: false, error: authResult.error || 'Authentication required' },
35+
{ status: 401 }
36+
)
37+
}
38+
39+
logger.info(
40+
`[${requestId}] Authenticated Dataverse upload request via ${authResult.authType}`,
41+
{
42+
userId: authResult.userId,
43+
}
44+
)
45+
46+
const body = await request.json()
47+
const validatedData = DataverseUploadFileSchema.parse(body)
48+
49+
logger.info(`[${requestId}] Uploading file to Dataverse`, {
50+
entitySetName: validatedData.entitySetName,
51+
recordId: validatedData.recordId,
52+
fileColumn: validatedData.fileColumn,
53+
fileName: validatedData.fileName,
54+
hasFile: !!validatedData.file,
55+
hasFileContent: !!validatedData.fileContent,
56+
})
57+
58+
let fileBuffer: Buffer
59+
60+
if (validatedData.file) {
61+
const rawFile = validatedData.file
62+
logger.info(`[${requestId}] Processing UserFile upload: ${rawFile.name}`)
63+
64+
let userFile
65+
try {
66+
userFile = processSingleFileToUserFile(rawFile, requestId, logger)
67+
} catch (error) {
68+
return NextResponse.json(
69+
{
70+
success: false,
71+
error: error instanceof Error ? error.message : 'Failed to process file',
72+
},
73+
{ status: 400 }
74+
)
75+
}
76+
77+
fileBuffer = await downloadFileFromStorage(userFile, requestId, logger)
78+
} else if (validatedData.fileContent) {
79+
fileBuffer = Buffer.from(validatedData.fileContent, 'base64')
80+
} else {
81+
return NextResponse.json(
82+
{ success: false, error: 'Either file or fileContent must be provided' },
83+
{ status: 400 }
84+
)
85+
}
86+
87+
const baseUrl = validatedData.environmentUrl.replace(/\/$/, '')
88+
const uploadUrl = `${baseUrl}/api/data/v9.2/${validatedData.entitySetName}(${validatedData.recordId})/${validatedData.fileColumn}?x-ms-file-name=${encodeURIComponent(validatedData.fileName)}`
89+
90+
const response = await fetch(uploadUrl, {
91+
method: 'PATCH',
92+
headers: {
93+
Authorization: `Bearer ${validatedData.accessToken}`,
94+
'Content-Type': 'application/octet-stream',
95+
'OData-MaxVersion': '4.0',
96+
'OData-Version': '4.0',
97+
'x-ms-file-name': validatedData.fileName,
98+
},
99+
body: new Uint8Array(fileBuffer),
100+
})
101+
102+
if (!response.ok) {
103+
const errorData = await response.json().catch(() => ({}))
104+
const errorMessage =
105+
errorData?.error?.message ??
106+
`Dataverse API error: ${response.status} ${response.statusText}`
107+
logger.error(`[${requestId}] Dataverse upload file failed`, {
108+
errorData,
109+
status: response.status,
110+
})
111+
return NextResponse.json({ success: false, error: errorMessage }, { status: response.status })
112+
}
113+
114+
logger.info(`[${requestId}] File uploaded to Dataverse successfully`, {
115+
entitySetName: validatedData.entitySetName,
116+
recordId: validatedData.recordId,
117+
fileColumn: validatedData.fileColumn,
118+
})
119+
120+
return NextResponse.json({
121+
success: true,
122+
output: {
123+
recordId: validatedData.recordId,
124+
fileColumn: validatedData.fileColumn,
125+
fileName: validatedData.fileName,
126+
success: true,
127+
},
128+
})
129+
} catch (error) {
130+
if (error instanceof z.ZodError) {
131+
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
132+
return NextResponse.json(
133+
{ success: false, error: 'Invalid request data', details: error.errors },
134+
{ status: 400 }
135+
)
136+
}
137+
138+
logger.error(`[${requestId}] Error uploading file to Dataverse:`, error)
139+
140+
return NextResponse.json(
141+
{ success: false, error: error instanceof Error ? error.message : 'Internal server error' },
142+
{ status: 500 }
143+
)
144+
}
145+
}

0 commit comments

Comments
 (0)