Skip to content

Commit ce9f80d

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 7b4bcd4 commit ce9f80d

6 files changed

Lines changed: 393 additions & 4 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: 44 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 } from './assets.types';
22
import { PaginatedResponse, NonPaginatedResponse, HasPaginationOptions } from '../../utils/pagination';
33

44
/**
@@ -90,4 +90,47 @@ 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+
* A `ValidationError` is thrown when the `newValue` type does not match.
109+
*
110+
* Returns void; the API does not return the updated asset, so call
111+
* {@link AssetServiceModel.getById} or {@link AssetServiceModel.getByName}
112+
* if you need the refreshed value.
113+
*
114+
* @param id - Asset ID
115+
* @param folderId - Required folder ID the asset belongs to
116+
* @param newValue - New value to apply (string for `Text`, number for `Integer`, boolean for `Bool`)
117+
* @returns Promise resolving when the asset has been updated
118+
*
119+
* @example
120+
* ```typescript
121+
* import { Assets } from '@uipath/uipath-typescript/assets';
122+
*
123+
* const assets = new Assets(sdk);
124+
*
125+
* // Update a Text asset
126+
* await assets.updateValueById(<assetId>, <folderId>, 'new-value');
127+
*
128+
* // Update an Integer asset
129+
* await assets.updateValueById(<assetId>, <folderId>, 42);
130+
*
131+
* // Update a Bool asset
132+
* await assets.updateValueById(<assetId>, <folderId>, true);
133+
* ```
134+
*/
135+
updateValueById(id: number, folderId: number, newValue: AssetNewValue): Promise<void>;
93136
}

src/models/orchestrator/assets.types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,13 @@ export interface AssetGetByIdOptions extends BaseOptions {}
7373
* Options for getting a single asset by name
7474
*/
7575
export interface AssetGetByNameOptions extends FolderScopedOptions {}
76+
77+
/**
78+
* New value accepted by {@link AssetServiceModel.updateValueById}.
79+
*
80+
* The runtime type must match the asset's `valueType`:
81+
* - `Text` → `string`
82+
* - `Integer` → `number`
83+
* - `Bool` → `boolean`
84+
*/
85+
export type AssetNewValue = string | number | boolean;

src/services/orchestrator/assets/assets.ts

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { FolderScopedService } from '../../folder-scoped';
2-
import { AssetGetResponse, AssetGetAllOptions, AssetGetByIdOptions, AssetGetByNameOptions } from '../../../models/orchestrator/assets.types';
2+
import { AssetGetResponse, AssetGetAllOptions, AssetGetByIdOptions, AssetGetByNameOptions, AssetNewValue, 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';
@@ -12,6 +12,7 @@ import { PaginatedResponse, NonPaginatedResponse, HasPaginationOptions } from '.
1212
import { PaginationHelpers } from '../../../utils/pagination/helpers';
1313
import { PaginationType } from '../../../utils/pagination/internal-types';
1414
import { track } from '../../../core/telemetry';
15+
import { ValidationError } from '../../../core/errors';
1516

1617
/**
1718
* Service for interacting with UiPath Orchestrator Assets API
@@ -155,4 +156,116 @@ export class AssetService extends FolderScopedService implements AssetServiceMod
155156
(raw) => transformData(pascalToCamelCaseKeys(raw), AssetMap),
156157
);
157158
}
159+
160+
/**
161+
* Updates the value of an existing asset by ID.
162+
*
163+
* Fetches the asset internally to determine its type, then updates only the value while
164+
* preserving the asset's name, scope, and description.
165+
*
166+
* **Supported value types:** `Text`, `Integer`, and `Bool` only. Other types
167+
* (`Credential`, `Secret`) throw a `ValidationError`.
168+
*
169+
* The `newValue` runtime type must match the asset's `valueType`:
170+
* - `Text` → `string`
171+
* - `Integer` → `number` (integer)
172+
* - `Bool` → `boolean`
173+
*
174+
* A `ValidationError` is thrown when the `newValue` type does not match.
175+
*
176+
* Returns void; the API does not return the updated asset, so call
177+
* {@link AssetService.getById} or {@link AssetService.getByName} if you need
178+
* the refreshed value.
179+
*
180+
* @param id - Asset ID
181+
* @param folderId - Required folder ID the asset belongs to
182+
* @param newValue - New value to apply (string for `Text`, number for `Integer`, boolean for `Bool`)
183+
* @returns Promise resolving when the asset has been updated
184+
*
185+
* @example
186+
* ```typescript
187+
* import { Assets } from '@uipath/uipath-typescript/assets';
188+
*
189+
* const assets = new Assets(sdk);
190+
*
191+
* // Update a Text asset
192+
* await assets.updateValueById(<assetId>, <folderId>, 'new-value');
193+
*
194+
* // Update an Integer asset
195+
* await assets.updateValueById(<assetId>, <folderId>, 42);
196+
*
197+
* // Update a Bool asset
198+
* await assets.updateValueById(<assetId>, <folderId>, true);
199+
* ```
200+
*/
201+
@track('Assets.UpdateValueById')
202+
async updateValueById(id: number, folderId: number, newValue: AssetNewValue): Promise<void> {
203+
if (!id) {
204+
throw new ValidationError({ message: 'id is required for updateValueById' });
205+
}
206+
if (!folderId) {
207+
throw new ValidationError({ message: 'folderId is required for updateValueById' });
208+
}
209+
if (newValue === null || newValue === undefined) {
210+
throw new ValidationError({ message: 'newValue is required for updateValueById' });
211+
}
212+
213+
const existing = await this.getById(id, folderId);
214+
215+
const valueField = resolveValueField(id, existing.valueType, newValue);
216+
217+
const headers = createHeaders({ [FOLDER_ID]: folderId });
218+
const body: Record<string, unknown> = {
219+
Id: id,
220+
Name: existing.name,
221+
ValueScope: existing.valueScope,
222+
ValueType: existing.valueType,
223+
Description: existing.description,
224+
[valueField]: newValue,
225+
};
226+
227+
await this.put(
228+
ASSET_ENDPOINTS.GET_BY_ID(id),
229+
body,
230+
{ headers },
231+
);
232+
}
233+
}
234+
235+
/**
236+
* Maps the asset's `valueType` to the PUT body field carrying the new value, validating
237+
* that the new value's runtime type matches the asset type.
238+
*/
239+
function resolveValueField(
240+
id: number,
241+
valueType: AssetValueType,
242+
newValue: AssetNewValue,
243+
): 'StringValue' | 'IntValue' | 'BoolValue' {
244+
switch (valueType) {
245+
case AssetValueType.Text:
246+
if (typeof newValue !== 'string') {
247+
throw new ValidationError({
248+
message: `Asset ${id} has valueType Text; newValue must be a string, got ${typeof newValue}`,
249+
});
250+
}
251+
return 'StringValue';
252+
case AssetValueType.Integer:
253+
if (typeof newValue !== 'number' || !Number.isInteger(newValue)) {
254+
throw new ValidationError({
255+
message: `Asset ${id} has valueType Integer; newValue must be an integer number, got ${typeof newValue}`,
256+
});
257+
}
258+
return 'IntValue';
259+
case AssetValueType.Bool:
260+
if (typeof newValue !== 'boolean') {
261+
throw new ValidationError({
262+
message: `Asset ${id} has valueType Bool; newValue must be a boolean, got ${typeof newValue}`,
263+
});
264+
}
265+
return 'BoolValue';
266+
default:
267+
throw new ValidationError({
268+
message: `updateValueById only supports Text, Integer, or Bool assets; asset ${id} has valueType ${valueType}`,
269+
});
270+
}
158271
}

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

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect } from 'vitest';
22
import { getServices, getTestConfig, setupUnifiedTests, InitMode } from '../../config/unified-setup';
3-
import { isNotFoundError } from '../../../../src/core/errors';
3+
import { isNotFoundError, isValidationError } from '../../../../src/core/errors';
44

55
const modes: InitMode[] = ['v0', 'v1'];
66

@@ -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,56 @@ 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, folderId, newValue);
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, folderId, previousValue);
195+
});
196+
197+
it('should throw ValidationError for invalid id', async () => {
198+
const { assets } = getServices();
199+
const config = getTestConfig();
200+
201+
if (!config.folderId) {
202+
throw new Error('INTEGRATION_TEST_FOLDER_ID must be configured to test updateValueById');
203+
}
204+
const folderId = Number(config.folderId);
205+
206+
await expect(
207+
assets.updateValueById(0, folderId, 'irrelevant'),
208+
).rejects.toSatisfy(isValidationError);
209+
});
210+
});
211+
162212
describe('Asset structure validation', () => {
163213
it('should have expected fields in asset objects', async () => {
164214
const { assets } = getServices();

0 commit comments

Comments
 (0)