Skip to content

Commit a4ea364

Browse files
feat(assets): add updateValueById for PUT /odata/Assets({key})
Adds Assets.updateValueById(id, folderId, newValue) that fetches the asset to determine its valueType, validates the newValue runtime type matches (Text/Integer/Bool only), and PUTs the update while preserving the asset's name, scope, and description. Unsupported valueTypes (Credential, Secret) throw ValidationError. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 887631a commit a4ea364

8 files changed

Lines changed: 404 additions & 9 deletions

File tree

docs/oauth-scopes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This page lists the specific OAuth scopes required in external app for each SDK
99
| `getAll()` | `OR.Assets` or `OR.Assets.Read` |
1010
| `getById()` | `OR.Assets` or `OR.Assets.Read` |
1111
| `getByName()` | `OR.Assets` or `OR.Assets.Read` |
12+
| `updateValueById()` | `OR.Assets` or `OR.Assets.Write` |
1213

1314
## Jobs
1415

src/models/orchestrator/assets.models.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AssetGetAllOptions, AssetGetResponse, AssetGetByIdOptions, AssetGetByNameOptions } from './assets.types';
1+
import { AssetGetAllOptions, AssetGetResponse, AssetGetByIdOptions, AssetGetByNameOptions, AssetNewValue, AssetUpdateValueByIdOptions } from './assets.types';
22
import { PaginatedResponse, NonPaginatedResponse, HasPaginationOptions } from '../../utils/pagination';
33

44
/**
@@ -90,4 +90,37 @@ export interface AssetServiceModel {
9090
* ```
9191
*/
9292
getByName(name: string, options?: AssetGetByNameOptions): Promise<AssetGetResponse>;
93+
94+
/**
95+
* Updates the value of an existing asset by ID.
96+
*
97+
* Fetches the asset internally to determine its type, then updates only the value while
98+
* preserving the asset's name, scope, and description.
99+
*
100+
* **Supported value types:** `Text`, `Integer`, and `Bool` only. Other types
101+
* (`Credential`, `Secret`) throw a `ValidationError`.
102+
*
103+
* The `newValue` runtime type must match the asset's `valueType`:
104+
* - `Text` → `string`
105+
* - `Integer` → `number` (integer)
106+
* - `Bool` → `boolean`
107+
*
108+
* @param id - Asset ID
109+
* @param newValue - New value to apply (string for `Text`, number for `Integer`, boolean for `Bool`)
110+
* @param options - Folder scoping (`folderId` / `folderKey` / `folderPath`)
111+
* @returns Promise resolving when the asset has been updated
112+
*
113+
* @example
114+
* ```typescript
115+
* // Update a Text asset by folder ID
116+
* await assets.updateValueById(<assetId>, 'new-value', { folderId: <folderId> });
117+
*
118+
* // Update an Integer asset by folder key (GUID)
119+
* await assets.updateValueById(<assetId>, 42, { folderKey: '5f6dadf1-3677-49dc-8aca-c2999dd4b3ba' });
120+
*
121+
* // Update a Bool asset by folder path
122+
* await assets.updateValueById(<assetId>, true, { folderPath: 'Shared/Finance' });
123+
* ```
124+
*/
125+
updateValueById(id: number, newValue: AssetNewValue, options?: AssetUpdateValueByIdOptions): Promise<void>;
93126
}

src/models/orchestrator/assets.types.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,10 @@ export enum AssetValueScope {
1313
* Enum for Asset Value Type
1414
*/
1515
export enum AssetValueType {
16-
DBConnectionString = 'DBConnectionString',
17-
HttpConnectionString = 'HttpConnectionString',
1816
Text = 'Text',
1917
Bool = 'Bool',
2018
Integer = 'Integer',
2119
Credential = 'Credential',
22-
WindowsCredential = 'WindowsCredential',
23-
KeyValueList = 'KeyValueList',
2420
Secret = 'Secret'
2521
}
2622

@@ -73,3 +69,18 @@ export interface AssetGetByIdOptions extends BaseOptions {}
7369
* Options for getting a single asset by name
7470
*/
7571
export interface AssetGetByNameOptions extends FolderScopedOptions {}
72+
73+
/**
74+
* New value accepted by {@link AssetServiceModel.updateValueById}.
75+
*
76+
* The runtime type must match the asset's `valueType`:
77+
* - `Text` → `string`
78+
* - `Integer` → `number`
79+
* - `Bool` → `boolean`
80+
*/
81+
export type AssetNewValue = string | number | boolean;
82+
83+
/**
84+
* Options for updating an asset value by ID
85+
*/
86+
export interface AssetUpdateValueByIdOptions extends FolderScopedOptions {}

src/services/orchestrator/assets/assets.ts

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { FolderScopedService } from '../../folder-scoped';
2-
import { AssetGetResponse, AssetGetAllOptions, AssetGetByIdOptions, AssetGetByNameOptions } from '../../../models/orchestrator/assets.types';
2+
import { AssetGetResponse, AssetGetAllOptions, AssetGetByIdOptions, AssetGetByNameOptions, AssetNewValue, AssetUpdateValueByIdOptions, AssetValueScope, AssetValueType } from '../../../models/orchestrator/assets.types';
33
import { AssetServiceModel } from '../../../models/orchestrator/assets.models';
44
import { addPrefixToKeys, pascalToCamelCaseKeys, transformData } from '../../../utils/transform';
55
import { createHeaders } from '../../../utils/http/headers';
66
import { FOLDER_ID } from '../../../utils/constants/headers';
7+
import { resolveFolderHeaders } from '../../../utils/folder/folder-headers';
78
import { ASSET_ENDPOINTS } from '../../../utils/constants/endpoints';
89
import { ODATA_PREFIX, ODATA_OFFSET_PARAMS } from '../../../utils/constants/common';
910
import { AssetMap } from '../../../models/orchestrator/assets.constants';
@@ -12,6 +13,7 @@ import { PaginatedResponse, NonPaginatedResponse, HasPaginationOptions } from '.
1213
import { PaginationHelpers } from '../../../utils/pagination/helpers';
1314
import { PaginationType } from '../../../utils/pagination/internal-types';
1415
import { track } from '../../../core/telemetry';
16+
import { ValidationError } from '../../../core/errors';
1517

1618
/**
1719
* Service for interacting with UiPath Orchestrator Assets API
@@ -155,4 +157,123 @@ export class AssetService extends FolderScopedService implements AssetServiceMod
155157
(raw) => transformData(pascalToCamelCaseKeys(raw), AssetMap),
156158
);
157159
}
160+
161+
/**
162+
* Updates the value of an existing asset by ID.
163+
*
164+
* Fetches the asset internally to determine its type, then updates only the value while
165+
* preserving the asset's name, scope, and description.
166+
*
167+
* **Supported value types:** `Text`, `Integer`, and `Bool` only. Other types
168+
* (`Credential`, `Secret`) throw a `ValidationError`.
169+
*
170+
* The `newValue` runtime type must match the asset's `valueType`:
171+
* - `Text` → `string`
172+
* - `Integer` → `number` (integer)
173+
* - `Bool` → `boolean`
174+
*
175+
* @param id - Asset ID
176+
* @param newValue - New value to apply (string for `Text`, number for `Integer`, boolean for `Bool`)
177+
* @param options - Folder scoping (`folderId` / `folderKey` / `folderPath`)
178+
* @returns Promise resolving when the asset has been updated
179+
*
180+
* @example
181+
* ```typescript
182+
* import { Assets } from '@uipath/uipath-typescript/assets';
183+
*
184+
* const assets = new Assets(sdk);
185+
*
186+
* // Update a Text asset by folder ID
187+
* await assets.updateValueById(<assetId>, 'new-value', { folderId: <folderId> });
188+
*
189+
* // Update an Integer asset by folder key (GUID)
190+
* await assets.updateValueById(<assetId>, 42, { folderKey: '5f6dadf1-3677-49dc-8aca-c2999dd4b3ba' });
191+
*
192+
* // Update a Bool asset by folder path
193+
* await assets.updateValueById(<assetId>, true, { folderPath: 'Shared/Finance' });
194+
* ```
195+
*/
196+
@track('Assets.UpdateValueById')
197+
async updateValueById(id: number, newValue: AssetNewValue, options?: AssetUpdateValueByIdOptions): Promise<void> {
198+
if (!id) {
199+
throw new ValidationError({ message: 'id is required for updateValueById' });
200+
}
201+
if (newValue === null || newValue === undefined) {
202+
throw new ValidationError({ message: 'newValue is required for updateValueById' });
203+
}
204+
205+
const headers = resolveFolderHeaders({
206+
folderId: options?.folderId,
207+
folderKey: options?.folderKey,
208+
folderPath: options?.folderPath,
209+
resourceType: 'Assets.updateValueById',
210+
fallbackFolderKey: this.config.folderKey,
211+
});
212+
213+
const existingResponse = await this.get<{
214+
Name: string;
215+
ValueScope: AssetValueScope;
216+
ValueType: AssetValueType;
217+
Description: string | null;
218+
}>(
219+
ASSET_ENDPOINTS.GET_BY_ID(id),
220+
{ headers },
221+
);
222+
const existing = existingResponse.data;
223+
224+
const valueField = resolveValueField(id, existing.ValueType, newValue);
225+
226+
const body: Record<string, unknown> = {
227+
Id: id,
228+
Name: existing.Name,
229+
ValueScope: existing.ValueScope,
230+
ValueType: existing.ValueType,
231+
Description: existing.Description,
232+
[valueField]: newValue,
233+
};
234+
235+
await this.put(
236+
ASSET_ENDPOINTS.GET_BY_ID(id),
237+
body,
238+
{ headers },
239+
);
240+
}
241+
}
242+
243+
/**
244+
* Maps the asset's `valueType` to the PUT body field carrying the new value, validating
245+
* that the new value's runtime type matches the asset type.
246+
*/
247+
function resolveValueField(
248+
id: number,
249+
valueType: AssetValueType,
250+
newValue: AssetNewValue,
251+
): 'StringValue' | 'IntValue' | 'BoolValue' {
252+
switch (valueType) {
253+
case AssetValueType.Text:
254+
if (typeof newValue !== 'string') {
255+
throw new ValidationError({
256+
message: `Asset ${id} has valueType Text; newValue must be a string, got ${typeof newValue}`,
257+
});
258+
}
259+
return 'StringValue';
260+
case AssetValueType.Integer:
261+
if (typeof newValue !== 'number' || !Number.isInteger(newValue)) {
262+
throw new ValidationError({
263+
message: `Asset ${id} has valueType Integer; newValue must be an integer number, got ${typeof newValue}`,
264+
});
265+
}
266+
return 'IntValue';
267+
case AssetValueType.Bool:
268+
if (typeof newValue !== 'boolean') {
269+
throw new ValidationError({
270+
message: `Asset ${id} has valueType Bool; newValue must be a boolean, got ${typeof newValue}`,
271+
});
272+
}
273+
return 'BoolValue';
274+
default:
275+
throw new ValidationError({
276+
message: `updateValueById only supports Text, Integer, or Bool assets; asset ${id} has valueType ${valueType}`,
277+
});
278+
}
158279
}

tests/integration/shared/orchestrator/assets.integration.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe.each(modes)('Orchestrator Assets - Integration Tests [%s]', (mode) => {
7777
expect(result.name).toBeDefined();
7878
expect(result.valueType).toBeDefined();
7979
expect(typeof result.name).toBe('string');
80-
});
80+
});
8181
});
8282

8383
describe('getByName', () => {
@@ -159,6 +159,42 @@ describe.each(modes)('Orchestrator Assets - Integration Tests [%s]', (mode) => {
159159
});
160160
});
161161

162+
describe('updateValueById', () => {
163+
it('should update an existing text asset and persist the change', async () => {
164+
const { assets } = getServices();
165+
const config = getTestConfig();
166+
167+
if (!config.folderId) {
168+
throw new Error('INTEGRATION_TEST_FOLDER_ID must be configured to test updateValueById');
169+
}
170+
const folderId = Number(config.folderId);
171+
172+
// Find an existing Text-type asset in the folder so we can update it safely
173+
const allAssets = await assets.getAll({
174+
folderId,
175+
pageSize: 20,
176+
filter: "ValueType eq 'Text'",
177+
});
178+
179+
if (allAssets.items.length === 0) {
180+
throw new Error('No Text-type assets available in the configured folder to exercise updateValueById');
181+
}
182+
183+
const target = allAssets.items[0];
184+
const previousValue = target.value ?? '';
185+
const newValue = `sdk-test-${Date.now()}`;
186+
187+
const result = await assets.updateValueById(target.id, newValue, { folderId });
188+
expect(result).toBeUndefined();
189+
190+
const refreshed = await assets.getById(target.id, folderId);
191+
expect(refreshed.value).toBe(newValue);
192+
193+
// Restore the asset so the suite is idempotent
194+
await assets.updateValueById(target.id, previousValue, { folderId });
195+
});
196+
});
197+
162198
describe('Asset structure validation', () => {
163199
it('should have expected fields in asset objects', async () => {
164200
const { assets } = getServices();

tests/integration/utils/cleanup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export async function cleanupTestTask(
5454
await retryWithBackoff(async () => {
5555
// Attempt to unassign if assigned
5656
try {
57-
await tasks.unassign({ taskIds: [taskId] });
57+
await tasks.unassign([taskId]);
5858
} catch {
5959
// Ignore if already unassigned or can't be unassigned
6060
}

0 commit comments

Comments
 (0)