@uipath/uipath-typescript 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/agent-memory/index.d.ts +4 -1
  2. package/dist/agents/index.cjs +341 -6
  3. package/dist/agents/index.d.ts +717 -16
  4. package/dist/agents/index.mjs +342 -7
  5. package/dist/assets/index.cjs +25 -9
  6. package/dist/assets/index.mjs +25 -9
  7. package/dist/attachments/index.cjs +25 -9
  8. package/dist/attachments/index.mjs +25 -9
  9. package/dist/buckets/index.cjs +25 -9
  10. package/dist/buckets/index.mjs +25 -9
  11. package/dist/cases/index.cjs +621 -524
  12. package/dist/cases/index.d.ts +186 -43
  13. package/dist/cases/index.mjs +621 -524
  14. package/dist/conversational-agent/index.cjs +25 -9
  15. package/dist/conversational-agent/index.mjs +25 -9
  16. package/dist/core/index.cjs +1 -1
  17. package/dist/core/index.mjs +1 -1
  18. package/dist/document-understanding/index.cjs +84 -84
  19. package/dist/document-understanding/index.d.ts +2 -1
  20. package/dist/document-understanding/index.mjs +1 -1
  21. package/dist/entities/index.cjs +25 -9
  22. package/dist/entities/index.mjs +25 -9
  23. package/dist/feedback/index.cjs +25 -9
  24. package/dist/feedback/index.mjs +25 -9
  25. package/dist/index.cjs +332 -62
  26. package/dist/index.d.ts +827 -89
  27. package/dist/index.mjs +333 -63
  28. package/dist/index.umd.js +332 -62
  29. package/dist/jobs/index.cjs +129 -14
  30. package/dist/jobs/index.d.ts +10 -5
  31. package/dist/jobs/index.mjs +129 -14
  32. package/dist/maestro-processes/index.cjs +198 -100
  33. package/dist/maestro-processes/index.d.ts +188 -43
  34. package/dist/maestro-processes/index.mjs +198 -100
  35. package/dist/processes/index.cjs +25 -9
  36. package/dist/processes/index.d.ts +6 -1
  37. package/dist/processes/index.mjs +25 -9
  38. package/dist/queues/index.cjs +25 -9
  39. package/dist/queues/index.mjs +25 -9
  40. package/dist/tasks/index.cjs +25 -9
  41. package/dist/tasks/index.d.ts +4 -1
  42. package/dist/tasks/index.mjs +25 -9
  43. package/dist/traces/index.cjs +2 -2
  44. package/dist/traces/index.d.ts +3 -3
  45. package/dist/traces/index.mjs +2 -2
  46. package/package.json +1 -1
@@ -42,6 +42,108 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
42
42
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
43
43
  };
44
44
 
45
+ /**
46
+ * Maps API field names (countOf*) to SDK field names (*Count) for InstanceStats,
47
+ * aligning naming with ElementStats and other count-suffixed conventions.
48
+ */
49
+ const InstanceStatsMap = {
50
+ countOfAllInstances: 'totalCount',
51
+ countOfRunning: 'runningCount',
52
+ countOfTransitioning: 'transitioningCount',
53
+ countOfPaused: 'pausedCount',
54
+ countOfFaulted: 'faultedCount',
55
+ countOfCompleted: 'completedCount',
56
+ countOfCancelled: 'cancelledCount',
57
+ countOfDeleted: 'deletedCount'
58
+ };
59
+
60
+ /**
61
+ * Converts a UTC timestamp string (e.g., "5/8/2026 11:20:17 AM") to ISO 8601 UTC format.
62
+ * Returns the original value if parsing fails.
63
+ */
64
+ /**
65
+ * Transforms data by renaming each key in `data` exactly once, using the
66
+ * mapping (`sourceField → targetField`). Keys not present in the mapping
67
+ * pass through unchanged. The original (pre-rename) key is dropped — the
68
+ * result contains only the renamed key.
69
+ *
70
+ * Each rename is independent. If the mapping happens to contain chained
71
+ * entries (`a → b` and `b → c`), they do NOT compose: a field named `a`
72
+ * in `data` becomes `b` (not `c`), because the renames are applied based
73
+ * on the original data's keys, not the running result.
74
+ *
75
+ * @param data The source data to transform
76
+ * @param fieldMapping Object mapping source field names to target field names
77
+ * @returns Transformed data with mapped field names
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * // Single object transformation
82
+ * const data = { id: '123', userName: 'john' };
83
+ * const mapping = { id: 'userId', userName: 'name' };
84
+ * const result = transformData(data, mapping);
85
+ * // result = { userId: '123', name: 'john' }
86
+ *
87
+ * // Array transformation
88
+ * const dataArray = [
89
+ * { id: '123', userName: 'john' },
90
+ * { id: '456', userName: 'jane' }
91
+ * ];
92
+ * const result = transformData(dataArray, mapping);
93
+ * // result = [
94
+ * // { userId: '123', name: 'john' },
95
+ * // { userId: '456', name: 'jane' }
96
+ * // ]
97
+ *
98
+ * // No chaining — `a → b` does not become `a → c` even if the map has `b → c`.
99
+ * transformData({ a: 1 }, { a: 'b', b: 'c' });
100
+ * // result = { b: 1 }
101
+ * ```
102
+ */
103
+ function transformData(data, fieldMapping) {
104
+ // Pass null/undefined through unchanged — callers (e.g. AttachmentService.getById)
105
+ // may invoke this on optional fields that an OData `select` excluded.
106
+ if (data == null) {
107
+ return data;
108
+ }
109
+ // Handle array of objects
110
+ if (Array.isArray(data)) {
111
+ return data.map(item => transformData(item, fieldMapping));
112
+ }
113
+ // Walk the ORIGINAL data's keys, look up each in the mapping. One rename
114
+ // per data key — no mutation of an in-progress result, so chains can't form.
115
+ const result = {};
116
+ for (const [key, value] of Object.entries(data)) {
117
+ const renamedKey = fieldMapping[key] ?? key;
118
+ result[renamedKey] = value;
119
+ }
120
+ return result;
121
+ }
122
+ /**
123
+ * Adds a prefix to specified keys in an object, returning a new object.
124
+ * Only the provided keys are prefixed; all others are left unchanged.
125
+ *
126
+ * @param obj The source object
127
+ * @param prefix The prefix to add (e.g., '$')
128
+ * @param keys The keys to prefix (e.g., ['expand', 'filter'])
129
+ * @returns A new object with specified keys prefixed
130
+ *
131
+ * @example
132
+ * addPrefixToKeys({ expand: 'a', foo: 1 }, '$', ['expand']) // { $expand: 'a', foo: 1 }
133
+ */
134
+ function addPrefixToKeys(obj, prefix, keys) {
135
+ const result = {};
136
+ for (const [key, value] of Object.entries(obj)) {
137
+ if (keys.includes(key)) {
138
+ result[`${prefix}${key}`] = value;
139
+ }
140
+ else {
141
+ result[key] = value;
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+
45
147
  /**
46
148
  * Base path constants for different services
47
149
  */
@@ -87,6 +189,8 @@ const MAESTRO_ENDPOINTS = {
87
189
  INSTANCE_STATUS_BY_DATE: `${INSIGHTS_RTM_BASE}/agenticInstanceStatus/InstanceStatusByDate`,
88
190
  /** Top processes ranked by total duration */
89
191
  TOP_PROCESSES_BY_DURATION: `${INSIGHTS_RTM_BASE}/agenticInstanceStatus/TopProcessesByDuration`,
192
+ /** Instance stats (counts by status + duration percentiles) */
193
+ INSTANCE_COUNT_BY_STATUS: `${INSIGHTS_RTM_BASE}/agenticInstanceStatus/InstanceCountByStatus`,
90
194
  /** Element count by status for agentic instances (process and case) */
91
195
  ELEMENT_COUNT_BY_STATUS: `${INSIGHTS_RTM_BASE}/agenticInstanceStatus/ElementCountByStatus`,
92
196
  },
@@ -117,7 +221,26 @@ function createProcessMethods(processData, service) {
117
221
  throw new Error('Process key is undefined');
118
222
  if (!processData.packageId)
119
223
  throw new Error('Package ID is undefined');
120
- return service.getElementStats(processData.processKey, processData.packageId, startTime, endTime, packageVersion);
224
+ return service.getElementStats({
225
+ processKey: processData.processKey,
226
+ packageId: processData.packageId,
227
+ packageVersion,
228
+ startTime,
229
+ endTime,
230
+ });
231
+ },
232
+ getInstanceStats(startTime, endTime, packageVersion) {
233
+ if (!processData.processKey)
234
+ throw new Error('Process key is undefined');
235
+ if (!processData.packageId)
236
+ throw new Error('Package ID is undefined');
237
+ return service.getInstanceStats({
238
+ processKey: processData.processKey,
239
+ packageId: processData.packageId,
240
+ packageVersion,
241
+ startTime,
242
+ endTime,
243
+ });
121
244
  }
122
245
  };
123
246
  }
@@ -180,24 +303,21 @@ async function fetchInstanceStatusTimeline(postFn, startTime, endTime, isCaseMan
180
303
  return response.data ?? [];
181
304
  }
182
305
  /**
183
- * Builds the request body for the ElementCountByStatus endpoint.
306
+ * Builds the commonParams request body for Insights RTM endpoints
307
+ * that filter by process key, package, time range, and version.
184
308
  *
185
- * @param processKey - Process key to filter by
186
- * @param packageId - Package identifier
187
- * @param startTime - Start of the time range to query
188
- * @param endTime - End of the time range to query
189
- * @param packageVersion - Package version to filter by
190
- * @returns Request body for the ElementCountByStatus endpoint
309
+ * @param request - Process scope + time range to aggregate over
310
+ * @returns Request body with commonParams
191
311
  * @internal
192
312
  */
193
- function buildElementCountByStatusBody(processKey, packageId, startTime, endTime, packageVersion) {
313
+ function buildInsightsCommonBody(request) {
194
314
  return {
195
315
  commonParams: {
196
- processKey,
197
- packageId,
198
- startTime: startTime.getTime(),
199
- endTime: endTime.getTime(),
200
- version: packageVersion
316
+ processKey: request.processKey,
317
+ packageId: request.packageId,
318
+ startTime: request.startTime.getTime(),
319
+ endTime: request.endTime.getTime(),
320
+ version: request.packageVersion
201
321
  }
202
322
  };
203
323
  }
@@ -256,77 +376,6 @@ const PROCESS_INSTANCE_TOKEN_PARAMS = {
256
376
  TOKEN_PARAM: 'nextPage'
257
377
  };
258
378
 
259
- /**
260
- * Converts a UTC timestamp string (e.g., "5/8/2026 11:20:17 AM") to ISO 8601 UTC format.
261
- * Returns the original value if parsing fails.
262
- */
263
- /**
264
- * Transforms data by mapping fields according to the provided field mapping
265
- * @param data The source data to transform
266
- * @param fieldMapping Object mapping source field names to target field names
267
- * @returns Transformed data with mapped field names
268
- *
269
- * @example
270
- * ```typescript
271
- * // Single object transformation
272
- * const data = { id: '123', userName: 'john' };
273
- * const mapping = { id: 'userId', userName: 'name' };
274
- * const result = transformData(data, mapping);
275
- * // result = { userId: '123', name: 'john' }
276
- *
277
- * // Array transformation
278
- * const dataArray = [
279
- * { id: '123', userName: 'john' },
280
- * { id: '456', userName: 'jane' }
281
- * ];
282
- * const result = transformData(dataArray, mapping);
283
- * // result = [
284
- * // { userId: '123', name: 'john' },
285
- * // { userId: '456', name: 'jane' }
286
- * // ]
287
- * ```
288
- */
289
- function transformData(data, fieldMapping) {
290
- // Handle array of objects
291
- if (Array.isArray(data)) {
292
- return data.map(item => transformData(item, fieldMapping));
293
- }
294
- // Handle single object
295
- const result = { ...data };
296
- for (const [sourceField, targetField] of Object.entries(fieldMapping)) {
297
- if (sourceField in result) {
298
- const value = result[sourceField];
299
- delete result[sourceField];
300
- result[targetField] = value;
301
- }
302
- }
303
- return result;
304
- }
305
- /**
306
- * Adds a prefix to specified keys in an object, returning a new object.
307
- * Only the provided keys are prefixed; all others are left unchanged.
308
- *
309
- * @param obj The source object
310
- * @param prefix The prefix to add (e.g., '$')
311
- * @param keys The keys to prefix (e.g., ['expand', 'filter'])
312
- * @returns A new object with specified keys prefixed
313
- *
314
- * @example
315
- * addPrefixToKeys({ expand: 'a', foo: 1 }, '$', ['expand']) // { $expand: 'a', foo: 1 }
316
- */
317
- function addPrefixToKeys(obj, prefix, keys) {
318
- const result = {};
319
- for (const [key, value] of Object.entries(obj)) {
320
- if (keys.includes(key)) {
321
- result[`${prefix}${key}`] = value;
322
- }
323
- else {
324
- result[key] = value;
325
- }
326
- }
327
- return result;
328
- }
329
-
330
379
  /**
331
380
  * Maps fields for Incident entities
332
381
  */
@@ -2822,22 +2871,22 @@ class MaestroProcessesService extends BaseService {
2822
2871
  * Returns per-element execution counts (success, fail, terminated, paused, in-progress) and
2823
2872
  * duration percentile metrics (min, max, avg, p50, p95, p99) for BPMN elements within a process.
2824
2873
  *
2825
- * @param processKey - Process key to filter by
2826
- * @param packageId - Package identifier
2827
- * @param startTime - Start of the time range to query
2828
- * @param endTime - End of the time range to query
2829
- * @param packageVersion - Package version to filter by
2874
+ * @param request - Process scope + time range to aggregate over
2830
2875
  * @returns Promise resolving to an array of {@link ElementStats}
2831
2876
  * @example
2832
2877
  * ```typescript
2833
- * // Get element metrics for a process
2834
- * const elements = await maestroProcesses.getElementStats(
2835
- * '<processKey>',
2836
- * '<packageId>',
2837
- * new Date('2026-04-01'),
2838
- * new Date(),
2839
- * '1.0.1'
2840
- * );
2878
+ * // First, list processes to find the processKey, packageId, and available versions
2879
+ * const processes = await maestroProcesses.getAll();
2880
+ * const process = processes[0];
2881
+ *
2882
+ * // Get element metrics for that process
2883
+ * const elements = await maestroProcesses.getElementStats({
2884
+ * processKey: process.processKey,
2885
+ * packageId: process.packageId,
2886
+ * packageVersion: process.packageVersions[0],
2887
+ * startTime: new Date('2026-04-01'),
2888
+ * endTime: new Date(),
2889
+ * });
2841
2890
  *
2842
2891
  * // Analyze element performance
2843
2892
  * for (const element of elements) {
@@ -2845,12 +2894,58 @@ class MaestroProcessesService extends BaseService {
2845
2894
  * console.log(` Success: ${element.successCount}, Failed: ${element.failCount}`);
2846
2895
  * console.log(` Avg duration: ${element.avgDurationMs}ms, P95: ${element.p95DurationMs}ms`);
2847
2896
  * }
2897
+ *
2898
+ * // Using bound method on a process — auto-fills processKey and packageId
2899
+ * const boundElements = await process.getElementStats(
2900
+ * new Date('2026-04-01'),
2901
+ * new Date(),
2902
+ * process.packageVersions[0]
2903
+ * );
2848
2904
  * ```
2849
2905
  */
2850
- async getElementStats(processKey, packageId, startTime, endTime, packageVersion) {
2851
- const { data } = await this.post(MAESTRO_ENDPOINTS.INSIGHTS.ELEMENT_COUNT_BY_STATUS, buildElementCountByStatusBody(processKey, packageId, startTime, endTime, packageVersion));
2906
+ async getElementStats(request) {
2907
+ const { data } = await this.post(MAESTRO_ENDPOINTS.INSIGHTS.ELEMENT_COUNT_BY_STATUS, buildInsightsCommonBody(request));
2852
2908
  return data ?? [];
2853
2909
  }
2910
+ /**
2911
+ * Get instance stats for a process.
2912
+ *
2913
+ * Returns total instance counts broken down by status (running, completed, faulted, etc.)
2914
+ * and the average execution duration for all instances of a process within a time range.
2915
+ *
2916
+ * @param request - Process scope + time range to aggregate over
2917
+ * @returns Promise resolving to {@link InstanceStats}
2918
+ * @example
2919
+ * ```typescript
2920
+ * // First, list processes to find the processKey, packageId, and available versions
2921
+ * const processes = await maestroProcesses.getAll();
2922
+ * const process = processes[0];
2923
+ *
2924
+ * // Get instance status breakdown for that process
2925
+ * const counts = await maestroProcesses.getInstanceStats({
2926
+ * processKey: process.processKey,
2927
+ * packageId: process.packageId,
2928
+ * packageVersion: process.packageVersions[0],
2929
+ * startTime: new Date('2026-04-01'),
2930
+ * endTime: new Date(),
2931
+ * });
2932
+ *
2933
+ * console.log(`Total: ${counts.totalCount}`);
2934
+ * console.log(`Running: ${counts.runningCount}, Completed: ${counts.completedCount}`);
2935
+ * console.log(`Faulted: ${counts.faultedCount}, Avg duration: ${counts.avgDurationMs}ms`);
2936
+ *
2937
+ * // Using bound method on a process — auto-fills processKey and packageId
2938
+ * const boundCounts = await process.getInstanceStats(
2939
+ * new Date('2026-04-01'),
2940
+ * new Date(),
2941
+ * process.packageVersions[0]
2942
+ * );
2943
+ * ```
2944
+ */
2945
+ async getInstanceStats(request) {
2946
+ const { data } = await this.post(MAESTRO_ENDPOINTS.INSIGHTS.INSTANCE_COUNT_BY_STATUS, buildInsightsCommonBody(request));
2947
+ return transformData(data, InstanceStatsMap);
2948
+ }
2854
2949
  }
2855
2950
  __decorate([
2856
2951
  track('MaestroProcesses.GetAll')
@@ -2876,6 +2971,9 @@ __decorate([
2876
2971
  __decorate([
2877
2972
  track('MaestroProcesses.GetElementStats')
2878
2973
  ], MaestroProcessesService.prototype, "getElementStats", null);
2974
+ __decorate([
2975
+ track('MaestroProcesses.GetInstanceStats')
2976
+ ], MaestroProcessesService.prototype, "getInstanceStats", null);
2879
2977
 
2880
2978
  /**
2881
2979
  * Service class for Maestro Process Incidents
@@ -956,7 +956,16 @@ const BUCKET_TOKEN_PARAMS = {
956
956
  * Returns the original value if parsing fails.
957
957
  */
958
958
  /**
959
- * Transforms data by mapping fields according to the provided field mapping
959
+ * Transforms data by renaming each key in `data` exactly once, using the
960
+ * mapping (`sourceField → targetField`). Keys not present in the mapping
961
+ * pass through unchanged. The original (pre-rename) key is dropped — the
962
+ * result contains only the renamed key.
963
+ *
964
+ * Each rename is independent. If the mapping happens to contain chained
965
+ * entries (`a → b` and `b → c`), they do NOT compose: a field named `a`
966
+ * in `data` becomes `b` (not `c`), because the renames are applied based
967
+ * on the original data's keys, not the running result.
968
+ *
960
969
  * @param data The source data to transform
961
970
  * @param fieldMapping Object mapping source field names to target field names
962
971
  * @returns Transformed data with mapped field names
@@ -979,21 +988,28 @@ const BUCKET_TOKEN_PARAMS = {
979
988
  * // { userId: '123', name: 'john' },
980
989
  * // { userId: '456', name: 'jane' }
981
990
  * // ]
991
+ *
992
+ * // No chaining — `a → b` does not become `a → c` even if the map has `b → c`.
993
+ * transformData({ a: 1 }, { a: 'b', b: 'c' });
994
+ * // result = { b: 1 }
982
995
  * ```
983
996
  */
984
997
  function transformData(data, fieldMapping) {
998
+ // Pass null/undefined through unchanged — callers (e.g. AttachmentService.getById)
999
+ // may invoke this on optional fields that an OData `select` excluded.
1000
+ if (data == null) {
1001
+ return data;
1002
+ }
985
1003
  // Handle array of objects
986
1004
  if (Array.isArray(data)) {
987
1005
  return data.map(item => transformData(item, fieldMapping));
988
1006
  }
989
- // Handle single object
990
- const result = { ...data };
991
- for (const [sourceField, targetField] of Object.entries(fieldMapping)) {
992
- if (sourceField in result) {
993
- const value = result[sourceField];
994
- delete result[sourceField];
995
- result[targetField] = value;
996
- }
1007
+ // Walk the ORIGINAL data's keys, look up each in the mapping. One rename
1008
+ // per data key — no mutation of an in-progress result, so chains can't form.
1009
+ const result = {};
1010
+ for (const [key, value] of Object.entries(data)) {
1011
+ const renamedKey = fieldMapping[key] ?? key;
1012
+ result[renamedKey] = value;
997
1013
  }
998
1014
  return result;
999
1015
  }
@@ -312,7 +312,10 @@ declare enum JobState {
312
312
  Successful = "Successful",
313
313
  Stopped = "Stopped",
314
314
  Suspended = "Suspended",
315
- Resumed = "Resumed"
315
+ Resumed = "Resumed",
316
+ Cancelled = "Cancelled",
317
+ /** Server-side fallback for an unrecognized or missing job state. */
318
+ Unknown = "Unknown"
316
319
  }
317
320
  interface BaseOptions {
318
321
  expand?: string;
@@ -670,6 +673,8 @@ interface ProcessStartResponse extends ProcessProperties, FolderProperties {
670
673
  createdTime: string;
671
674
  startingScheduleId: number | null;
672
675
  processName: string;
676
+ /** Key of the process this job was started from. */
677
+ processKey: string;
673
678
  type: JobType;
674
679
  inputFile: string | null;
675
680
  outputArguments: string | null;
@@ -954,7 +954,16 @@ const BUCKET_TOKEN_PARAMS = {
954
954
  * Returns the original value if parsing fails.
955
955
  */
956
956
  /**
957
- * Transforms data by mapping fields according to the provided field mapping
957
+ * Transforms data by renaming each key in `data` exactly once, using the
958
+ * mapping (`sourceField → targetField`). Keys not present in the mapping
959
+ * pass through unchanged. The original (pre-rename) key is dropped — the
960
+ * result contains only the renamed key.
961
+ *
962
+ * Each rename is independent. If the mapping happens to contain chained
963
+ * entries (`a → b` and `b → c`), they do NOT compose: a field named `a`
964
+ * in `data` becomes `b` (not `c`), because the renames are applied based
965
+ * on the original data's keys, not the running result.
966
+ *
958
967
  * @param data The source data to transform
959
968
  * @param fieldMapping Object mapping source field names to target field names
960
969
  * @returns Transformed data with mapped field names
@@ -977,21 +986,28 @@ const BUCKET_TOKEN_PARAMS = {
977
986
  * // { userId: '123', name: 'john' },
978
987
  * // { userId: '456', name: 'jane' }
979
988
  * // ]
989
+ *
990
+ * // No chaining — `a → b` does not become `a → c` even if the map has `b → c`.
991
+ * transformData({ a: 1 }, { a: 'b', b: 'c' });
992
+ * // result = { b: 1 }
980
993
  * ```
981
994
  */
982
995
  function transformData(data, fieldMapping) {
996
+ // Pass null/undefined through unchanged — callers (e.g. AttachmentService.getById)
997
+ // may invoke this on optional fields that an OData `select` excluded.
998
+ if (data == null) {
999
+ return data;
1000
+ }
983
1001
  // Handle array of objects
984
1002
  if (Array.isArray(data)) {
985
1003
  return data.map(item => transformData(item, fieldMapping));
986
1004
  }
987
- // Handle single object
988
- const result = { ...data };
989
- for (const [sourceField, targetField] of Object.entries(fieldMapping)) {
990
- if (sourceField in result) {
991
- const value = result[sourceField];
992
- delete result[sourceField];
993
- result[targetField] = value;
994
- }
1005
+ // Walk the ORIGINAL data's keys, look up each in the mapping. One rename
1006
+ // per data key — no mutation of an in-progress result, so chains can't form.
1007
+ const result = {};
1008
+ for (const [key, value] of Object.entries(data)) {
1009
+ const renamedKey = fieldMapping[key] ?? key;
1010
+ result[renamedKey] = value;
995
1011
  }
996
1012
  return result;
997
1013
  }
@@ -956,7 +956,16 @@ const BUCKET_TOKEN_PARAMS = {
956
956
  * Returns the original value if parsing fails.
957
957
  */
958
958
  /**
959
- * Transforms data by mapping fields according to the provided field mapping
959
+ * Transforms data by renaming each key in `data` exactly once, using the
960
+ * mapping (`sourceField → targetField`). Keys not present in the mapping
961
+ * pass through unchanged. The original (pre-rename) key is dropped — the
962
+ * result contains only the renamed key.
963
+ *
964
+ * Each rename is independent. If the mapping happens to contain chained
965
+ * entries (`a → b` and `b → c`), they do NOT compose: a field named `a`
966
+ * in `data` becomes `b` (not `c`), because the renames are applied based
967
+ * on the original data's keys, not the running result.
968
+ *
960
969
  * @param data The source data to transform
961
970
  * @param fieldMapping Object mapping source field names to target field names
962
971
  * @returns Transformed data with mapped field names
@@ -979,21 +988,28 @@ const BUCKET_TOKEN_PARAMS = {
979
988
  * // { userId: '123', name: 'john' },
980
989
  * // { userId: '456', name: 'jane' }
981
990
  * // ]
991
+ *
992
+ * // No chaining — `a → b` does not become `a → c` even if the map has `b → c`.
993
+ * transformData({ a: 1 }, { a: 'b', b: 'c' });
994
+ * // result = { b: 1 }
982
995
  * ```
983
996
  */
984
997
  function transformData(data, fieldMapping) {
998
+ // Pass null/undefined through unchanged — callers (e.g. AttachmentService.getById)
999
+ // may invoke this on optional fields that an OData `select` excluded.
1000
+ if (data == null) {
1001
+ return data;
1002
+ }
985
1003
  // Handle array of objects
986
1004
  if (Array.isArray(data)) {
987
1005
  return data.map(item => transformData(item, fieldMapping));
988
1006
  }
989
- // Handle single object
990
- const result = { ...data };
991
- for (const [sourceField, targetField] of Object.entries(fieldMapping)) {
992
- if (sourceField in result) {
993
- const value = result[sourceField];
994
- delete result[sourceField];
995
- result[targetField] = value;
996
- }
1007
+ // Walk the ORIGINAL data's keys, look up each in the mapping. One rename
1008
+ // per data key — no mutation of an in-progress result, so chains can't form.
1009
+ const result = {};
1010
+ for (const [key, value] of Object.entries(data)) {
1011
+ const renamedKey = fieldMapping[key] ?? key;
1012
+ result[renamedKey] = value;
997
1013
  }
998
1014
  return result;
999
1015
  }
@@ -954,7 +954,16 @@ const BUCKET_TOKEN_PARAMS = {
954
954
  * Returns the original value if parsing fails.
955
955
  */
956
956
  /**
957
- * Transforms data by mapping fields according to the provided field mapping
957
+ * Transforms data by renaming each key in `data` exactly once, using the
958
+ * mapping (`sourceField → targetField`). Keys not present in the mapping
959
+ * pass through unchanged. The original (pre-rename) key is dropped — the
960
+ * result contains only the renamed key.
961
+ *
962
+ * Each rename is independent. If the mapping happens to contain chained
963
+ * entries (`a → b` and `b → c`), they do NOT compose: a field named `a`
964
+ * in `data` becomes `b` (not `c`), because the renames are applied based
965
+ * on the original data's keys, not the running result.
966
+ *
958
967
  * @param data The source data to transform
959
968
  * @param fieldMapping Object mapping source field names to target field names
960
969
  * @returns Transformed data with mapped field names
@@ -977,21 +986,28 @@ const BUCKET_TOKEN_PARAMS = {
977
986
  * // { userId: '123', name: 'john' },
978
987
  * // { userId: '456', name: 'jane' }
979
988
  * // ]
989
+ *
990
+ * // No chaining — `a → b` does not become `a → c` even if the map has `b → c`.
991
+ * transformData({ a: 1 }, { a: 'b', b: 'c' });
992
+ * // result = { b: 1 }
980
993
  * ```
981
994
  */
982
995
  function transformData(data, fieldMapping) {
996
+ // Pass null/undefined through unchanged — callers (e.g. AttachmentService.getById)
997
+ // may invoke this on optional fields that an OData `select` excluded.
998
+ if (data == null) {
999
+ return data;
1000
+ }
983
1001
  // Handle array of objects
984
1002
  if (Array.isArray(data)) {
985
1003
  return data.map(item => transformData(item, fieldMapping));
986
1004
  }
987
- // Handle single object
988
- const result = { ...data };
989
- for (const [sourceField, targetField] of Object.entries(fieldMapping)) {
990
- if (sourceField in result) {
991
- const value = result[sourceField];
992
- delete result[sourceField];
993
- result[targetField] = value;
994
- }
1005
+ // Walk the ORIGINAL data's keys, look up each in the mapping. One rename
1006
+ // per data key — no mutation of an in-progress result, so chains can't form.
1007
+ const result = {};
1008
+ for (const [key, value] of Object.entries(data)) {
1009
+ const renamedKey = fieldMapping[key] ?? key;
1010
+ result[renamedKey] = value;
995
1011
  }
996
1012
  return result;
997
1013
  }
@@ -715,7 +715,16 @@ var PaginationType;
715
715
  * Returns the original value if parsing fails.
716
716
  */
717
717
  /**
718
- * Transforms data by mapping fields according to the provided field mapping
718
+ * Transforms data by renaming each key in `data` exactly once, using the
719
+ * mapping (`sourceField → targetField`). Keys not present in the mapping
720
+ * pass through unchanged. The original (pre-rename) key is dropped — the
721
+ * result contains only the renamed key.
722
+ *
723
+ * Each rename is independent. If the mapping happens to contain chained
724
+ * entries (`a → b` and `b → c`), they do NOT compose: a field named `a`
725
+ * in `data` becomes `b` (not `c`), because the renames are applied based
726
+ * on the original data's keys, not the running result.
727
+ *
719
728
  * @param data The source data to transform
720
729
  * @param fieldMapping Object mapping source field names to target field names
721
730
  * @returns Transformed data with mapped field names
@@ -738,21 +747,28 @@ var PaginationType;
738
747
  * // { userId: '123', name: 'john' },
739
748
  * // { userId: '456', name: 'jane' }
740
749
  * // ]
750
+ *
751
+ * // No chaining — `a → b` does not become `a → c` even if the map has `b → c`.
752
+ * transformData({ a: 1 }, { a: 'b', b: 'c' });
753
+ * // result = { b: 1 }
741
754
  * ```
742
755
  */
743
756
  function transformData(data, fieldMapping) {
757
+ // Pass null/undefined through unchanged — callers (e.g. AttachmentService.getById)
758
+ // may invoke this on optional fields that an OData `select` excluded.
759
+ if (data == null) {
760
+ return data;
761
+ }
744
762
  // Handle array of objects
745
763
  if (Array.isArray(data)) {
746
764
  return data.map(item => transformData(item, fieldMapping));
747
765
  }
748
- // Handle single object
749
- const result = { ...data };
750
- for (const [sourceField, targetField] of Object.entries(fieldMapping)) {
751
- if (sourceField in result) {
752
- const value = result[sourceField];
753
- delete result[sourceField];
754
- result[targetField] = value;
755
- }
766
+ // Walk the ORIGINAL data's keys, look up each in the mapping. One rename
767
+ // per data key — no mutation of an in-progress result, so chains can't form.
768
+ const result = {};
769
+ for (const [key, value] of Object.entries(data)) {
770
+ const renamedKey = fieldMapping[key] ?? key;
771
+ result[renamedKey] = value;
756
772
  }
757
773
  return result;
758
774
  }