@open-mercato/core 0.10.1-develop.7815.1.94bb61610f → 0.10.1-develop.7821.1.e5115777cb

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 (40) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/communication_channels/workers/poll-channel.js +44 -16
  3. package/dist/modules/communication_channels/workers/poll-channel.js.map +2 -2
  4. package/dist/modules/sales/commands/documents.js +1 -1
  5. package/dist/modules/sales/commands/documents.js.map +2 -2
  6. package/dist/modules/workflows/api/definitions/[id]/route.js +15 -0
  7. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  8. package/dist/modules/workflows/api/definitions/generate/route.js +3 -1
  9. package/dist/modules/workflows/api/definitions/generate/route.js.map +2 -2
  10. package/dist/modules/workflows/api/definitions/route.js +15 -0
  11. package/dist/modules/workflows/api/definitions/route.js.map +2 -2
  12. package/dist/modules/workflows/lib/agent-result-mapping.js +3 -14
  13. package/dist/modules/workflows/lib/agent-result-mapping.js.map +2 -2
  14. package/dist/modules/workflows/lib/definition-evaluation.js +16 -2
  15. package/dist/modules/workflows/lib/definition-evaluation.js.map +2 -2
  16. package/dist/modules/workflows/lib/mapping-target-validation.js +85 -0
  17. package/dist/modules/workflows/lib/mapping-target-validation.js.map +7 -0
  18. package/dist/modules/workflows/lib/parallel-handler.js +10 -8
  19. package/dist/modules/workflows/lib/parallel-handler.js.map +2 -2
  20. package/dist/modules/workflows/lib/safe-mapping-path.js +38 -0
  21. package/dist/modules/workflows/lib/safe-mapping-path.js.map +7 -0
  22. package/dist/modules/workflows/lib/step-handler.js +5 -16
  23. package/dist/modules/workflows/lib/step-handler.js.map +2 -2
  24. package/package.json +8 -8
  25. package/src/modules/communication_channels/workers/poll-channel.ts +34 -5
  26. package/src/modules/sales/commands/documents.ts +3 -1
  27. package/src/modules/workflows/api/definitions/[id]/route.ts +17 -0
  28. package/src/modules/workflows/api/definitions/generate/route.ts +3 -1
  29. package/src/modules/workflows/api/definitions/route.ts +15 -0
  30. package/src/modules/workflows/i18n/de.json +1 -0
  31. package/src/modules/workflows/i18n/en.json +1 -0
  32. package/src/modules/workflows/i18n/es.json +1 -0
  33. package/src/modules/workflows/i18n/ko.json +1 -0
  34. package/src/modules/workflows/i18n/pl.json +1 -0
  35. package/src/modules/workflows/lib/agent-result-mapping.ts +3 -16
  36. package/src/modules/workflows/lib/definition-evaluation.ts +19 -2
  37. package/src/modules/workflows/lib/mapping-target-validation.ts +113 -0
  38. package/src/modules/workflows/lib/parallel-handler.ts +13 -9
  39. package/src/modules/workflows/lib/safe-mapping-path.ts +44 -0
  40. package/src/modules/workflows/lib/step-handler.ts +5 -32
@@ -28,6 +28,12 @@ import {
28
28
  } from '../openapi'
29
29
  import { invalidateTriggerCache } from '../../lib/event-trigger-service'
30
30
  import { normalizeDefinitionValidationIssues } from '../../lib/definition-error-body'
31
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
32
+ import {
33
+ mappingTargetIssuesForApi,
34
+ UNSAFE_MAPPING_TARGET_MESSAGE,
35
+ UNSAFE_MAPPING_TARGET_MESSAGE_KEY,
36
+ } from '../../lib/mapping-target-validation'
31
37
  import { getAllCodeWorkflows } from '../../lib/code-registry'
32
38
  import {
33
39
  authorizeWorkflowGrantChange,
@@ -262,6 +268,15 @@ export async function POST(request: NextRequest) {
262
268
  }
263
269
 
264
270
  const input: CreateWorkflowDefinitionApiInput = validation.data
271
+ const { translate } = await resolveTranslations()
272
+ const mappingMessage = translate(UNSAFE_MAPPING_TARGET_MESSAGE_KEY, UNSAFE_MAPPING_TARGET_MESSAGE)
273
+ const mappingIssues = mappingTargetIssuesForApi(input.definition, ['definition'], mappingMessage)
274
+ if (mappingIssues.length > 0) {
275
+ return NextResponse.json(
276
+ { error: 'Validation failed', details: mappingIssues },
277
+ { status: 400 }
278
+ )
279
+ }
265
280
 
266
281
  // New definitions default to strict interpolation (spec §3.6). The default
267
282
  // lives on the create path only — a schema-level default would flip
@@ -2029,6 +2029,7 @@
2029
2029
  "workflows.validation.timerDurationOrUntilRequired": "„Auf Timer warten“ erfordert entweder „Dauer“ oder „Warten bis“.",
2030
2030
  "workflows.validation.timerDurationXorUntil": "„Auf Timer warten“ akzeptiert „Dauer“ oder „Warten bis“, nicht beides. Löschen Sie eines davon.",
2031
2031
  "workflows.validation.uniqueId": "ID muss eindeutig sein",
2032
+ "workflows.validation.unsafeMappingTarget": "Zielpfade für Zuordnungen dürfen keine Segmente namens __proto__, constructor oder prototype enthalten",
2032
2033
  "workflows.validation.untilMustBeFuture": "„Warten bis“ muss ein Datum und eine Uhrzeit in der Zukunft sein.",
2033
2034
  "workflows.validation.workflowIdFormat": "Workflow-ID darf nur Kleinbuchstaben, Zahlen, Bindestriche und Unterstriche enthalten",
2034
2035
  "workflows.validation.workflowIdMaxLength": "Workflow-ID darf maximal 50 Zeichen lang sein",
@@ -2029,6 +2029,7 @@
2029
2029
  "workflows.validation.timerDurationOrUntilRequired": "Wait For Timer requires either \"Duration\" or \"Wait Until\".",
2030
2030
  "workflows.validation.timerDurationXorUntil": "Wait For Timer accepts \"Duration\" or \"Wait Until\", not both. Clear one of them.",
2031
2031
  "workflows.validation.uniqueId": "ID must be unique",
2032
+ "workflows.validation.unsafeMappingTarget": "Mapping target paths cannot contain __proto__, constructor, or prototype segments",
2032
2033
  "workflows.validation.untilMustBeFuture": "Wait Until must be a date and time in the future.",
2033
2034
  "workflows.validation.workflowIdFormat": "Workflow ID must contain only lowercase letters, numbers, hyphens, and underscores",
2034
2035
  "workflows.validation.workflowIdMaxLength": "Workflow ID must be 50 characters or less",
@@ -2029,6 +2029,7 @@
2029
2029
  "workflows.validation.timerDurationOrUntilRequired": "Esperar temporizador requiere una Duración o Esperar hasta.",
2030
2030
  "workflows.validation.timerDurationXorUntil": "Esperar temporizador acepta Duración o Esperar hasta, no ambos. Borra uno de ellos.",
2031
2031
  "workflows.validation.uniqueId": "El ID debe ser unico",
2032
+ "workflows.validation.unsafeMappingTarget": "Las rutas de destino de asignación no pueden contener segmentos __proto__, constructor o prototype",
2032
2033
  "workflows.validation.untilMustBeFuture": "Esperar hasta debe ser una fecha y hora futura.",
2033
2034
  "workflows.validation.workflowIdFormat": "El ID del flujo de trabajo solo puede contener letras minusculas, numeros, guiones y guiones bajos",
2034
2035
  "workflows.validation.workflowIdMaxLength": "El ID del flujo de trabajo debe tener 50 caracteres o menos",
@@ -2029,6 +2029,7 @@
2029
2029
  "workflows.validation.timerDurationOrUntilRequired": "Wait For Timer requires either \"Duration\" or \"Wait Until\".",
2030
2030
  "workflows.validation.timerDurationXorUntil": "Wait For Timer accepts \"Duration\" or \"Wait Until\", not both. Clear one of them.",
2031
2031
  "workflows.validation.uniqueId": "ID는 고유해야 합니다",
2032
+ "workflows.validation.unsafeMappingTarget": "매핑 대상 경로에는 __proto__, constructor 또는 prototype 세그먼트를 포함할 수 없습니다",
2032
2033
  "workflows.validation.untilMustBeFuture": "대기 종료 시각은 미래의 날짜와 시간이어야 합니다.",
2033
2034
  "workflows.validation.workflowIdFormat": "워크플로우 ID는 소문자, 숫자, 하이픈, 밑줄만 포함해야 합니다",
2034
2035
  "workflows.validation.workflowIdMaxLength": "워크플로우 ID는 50자 이하여야 합니다",
@@ -2029,6 +2029,7 @@
2029
2029
  "workflows.validation.timerDurationOrUntilRequired": "Krok „Czekaj na timer” wymaga podania „Czasu trwania” lub „Czekaj do”.",
2030
2030
  "workflows.validation.timerDurationXorUntil": "Krok „Czekaj na timer” przyjmuje „Czas trwania” albo „Czekaj do”, nie oba naraz. Wyczyść jedno z nich.",
2031
2031
  "workflows.validation.uniqueId": "ID musi być unikalne",
2032
+ "workflows.validation.unsafeMappingTarget": "Ścieżki docelowe mapowania nie mogą zawierać segmentów __proto__, constructor ani prototype",
2032
2033
  "workflows.validation.untilMustBeFuture": "„Czekaj do” musi być datą i godziną w przyszłości.",
2033
2034
  "workflows.validation.workflowIdFormat": "ID przepływu może zawierać tylko małe litery, cyfry, myślniki i podkreślenia",
2034
2035
  "workflows.validation.workflowIdMaxLength": "ID przepływu musi mieć 50 znaków lub mniej",
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import { createLogger } from '@open-mercato/shared/lib/logger'
17
+ import { safeGetNestedValue, safeSetNestedValue } from './safe-mapping-path'
17
18
 
18
19
  const logger = createLogger('workflows').child({ component: 'agent-result-mapping' })
19
20
 
@@ -27,20 +28,6 @@ export type AgentResultEnvelope = {
27
28
  artifacts?: unknown[]
28
29
  }
29
30
 
30
- function getNestedValue(obj: any, path: string): any {
31
- return path.split('.').reduce((current, key) => current?.[key], obj)
32
- }
33
-
34
- function setNestedValue(obj: Record<string, any>, path: string, value: any): void {
35
- const keys = path.split('.')
36
- const lastKey = keys.pop()!
37
- const target = keys.reduce<Record<string, any>>((current, key) => {
38
- if (!(key in current)) current[key] = {}
39
- return current[key]
40
- }, obj)
41
- target[lastKey] = value
42
- }
43
-
44
31
  function warnOnTemplateMapping(sourcePath: string): void {
45
32
  if (/\{\{.*\}\}/.test(sourcePath)) {
46
33
  logger.warn(
@@ -79,9 +66,9 @@ export function mapAgentResultToContext(
79
66
  const result: Record<string, any> = {}
80
67
  for (const [targetKey, sourcePath] of Object.entries(outputMapping)) {
81
68
  warnOnTemplateMapping(sourcePath)
82
- const value = getNestedValue(source, sourcePath)
69
+ const value = safeGetNestedValue(source, sourcePath)
83
70
  if (value !== undefined) {
84
- setNestedValue(result, targetKey, value)
71
+ safeSetNestedValue(result, targetKey, value)
85
72
  }
86
73
  }
87
74
  return result
@@ -31,6 +31,12 @@ import {
31
31
  } from './definition-error-body'
32
32
  import { collectUnresolvedContextRefWarnings } from './expression-refs'
33
33
  import { definitionToGraph, validateWorkflowGraph } from './graph-utils'
34
+ import {
35
+ collectUnsafeMappingTargetIssues,
36
+ mappingTargetIssuesForApi,
37
+ UNSAFE_MAPPING_TARGET_MESSAGE,
38
+ UNSAFE_MAPPING_TARGET_MESSAGE_KEY,
39
+ } from './mapping-target-validation'
34
40
 
35
41
  /**
36
42
  * Problems-panel copy for an unresolved `{{context.*}}` reference. Shared so
@@ -75,6 +81,7 @@ export interface EvaluateWorkflowDefinitionOptions {
75
81
  * those warnings are skipped (they are advisory and never blocking).
76
82
  */
77
83
  ledger?: ContextLedger | null
84
+ translate?: WorkflowIssueTranslator
78
85
  }
79
86
 
80
87
  export interface WorkflowDefinitionEvaluation {
@@ -128,7 +135,14 @@ export function evaluateWorkflowDefinition(
128
135
  const { nodes, edges } = definitionToGraph(definition, { autoLayout: false })
129
136
  const graphErrors = validateWorkflowGraph(nodes, edges)
130
137
  const parsed = workflowDefinitionDataSchema.safeParse(definition)
131
- const zodIssues: ZodIssueLike[] = parsed.success ? [] : parsed.error.issues
138
+ const mappingMessage = options.translate
139
+ ? options.translate(UNSAFE_MAPPING_TARGET_MESSAGE_KEY, UNSAFE_MAPPING_TARGET_MESSAGE, {})
140
+ : UNSAFE_MAPPING_TARGET_MESSAGE
141
+ const mappingIssues = collectUnsafeMappingTargetIssues(definition, mappingMessage)
142
+ const zodIssues: ZodIssueLike[] = [
143
+ ...(parsed.success ? [] : parsed.error.issues),
144
+ ...mappingIssues,
145
+ ]
132
146
  const ledger = options.ledger ?? null
133
147
  const configWarnings: ZodIssueLike[] = [
134
148
  ...collectActivityConfigWarnings(definition),
@@ -150,7 +164,10 @@ export function evaluateWorkflowDefinition(
150
164
  valid: errors === 0,
151
165
  errorCount: errors,
152
166
  warningCount: warnings,
153
- issues: parsed.success ? [] : normalizeDefinitionValidationIssues(parsed.error),
167
+ issues: [
168
+ ...(parsed.success ? [] : normalizeDefinitionValidationIssues(parsed.error)),
169
+ ...mappingTargetIssuesForApi(definition, [], mappingMessage),
170
+ ],
154
171
  problems,
155
172
  }
156
173
  }
@@ -0,0 +1,113 @@
1
+ import type { WorkflowDefinitionData } from '../data/entities'
2
+ import type { DefinitionValidationIssue } from './definition-error-body'
3
+ import type { ZodIssueLike } from './collect-validation-issues'
4
+ import { isSafeMappingTargetPath } from './safe-mapping-path'
5
+
6
+ export const UNSAFE_MAPPING_TARGET_MESSAGE_KEY = 'workflows.validation.unsafeMappingTarget'
7
+
8
+ export const UNSAFE_MAPPING_TARGET_MESSAGE =
9
+ 'Mapping target paths cannot contain __proto__, constructor, or prototype segments'
10
+
11
+ function isRecord(value: unknown): value is Record<string, unknown> {
12
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
13
+ }
14
+
15
+ function collectMappingIssues(
16
+ value: unknown,
17
+ path: Array<string | number>,
18
+ message: string,
19
+ ): ZodIssueLike[] {
20
+ if (!isRecord(value)) return []
21
+ return Object.keys(value)
22
+ .filter((targetPath) => !isSafeMappingTargetPath(targetPath))
23
+ .map((targetPath) => ({
24
+ path: [...path, targetPath],
25
+ message,
26
+ }))
27
+ }
28
+
29
+ function collectActivityIssues(
30
+ activities: unknown,
31
+ path: Array<string | number>,
32
+ message: string,
33
+ ): ZodIssueLike[] {
34
+ if (!Array.isArray(activities)) return []
35
+ return activities.flatMap((activity, activityIndex) => {
36
+ if (!isRecord(activity) || activity.activityType !== 'INVOKE_AGENT') return []
37
+ const config = isRecord(activity.config) ? activity.config : {}
38
+ return collectMappingIssues(config.outputMapping, [
39
+ ...path,
40
+ activityIndex,
41
+ 'config',
42
+ 'outputMapping',
43
+ ], message)
44
+ })
45
+ }
46
+
47
+ export function collectUnsafeMappingTargetIssues(
48
+ definition: WorkflowDefinitionData,
49
+ message = UNSAFE_MAPPING_TARGET_MESSAGE,
50
+ ): ZodIssueLike[] {
51
+ const stepIssues = Array.isArray(definition.steps)
52
+ ? definition.steps.flatMap((step, stepIndex) => {
53
+ if (!isRecord(step)) return []
54
+ const config = isRecord(step.config) ? step.config : {}
55
+ const subWorkflowConfig = isRecord(step.subWorkflowConfig) ? step.subWorkflowConfig : {}
56
+ const issues: ZodIssueLike[] = []
57
+
58
+ if (step.stepType === 'SUB_WORKFLOW') {
59
+ issues.push(
60
+ ...collectMappingIssues(config.inputMapping, ['steps', stepIndex, 'config', 'inputMapping'], message),
61
+ ...collectMappingIssues(config.outputMapping, ['steps', stepIndex, 'config', 'outputMapping'], message),
62
+ ...collectMappingIssues(subWorkflowConfig.inputMapping, [
63
+ 'steps',
64
+ stepIndex,
65
+ 'subWorkflowConfig',
66
+ 'inputMapping',
67
+ ], message),
68
+ ...collectMappingIssues(subWorkflowConfig.outputMapping, [
69
+ 'steps',
70
+ stepIndex,
71
+ 'subWorkflowConfig',
72
+ 'outputMapping',
73
+ ], message),
74
+ )
75
+ }
76
+ if (step.stepType === 'PARALLEL_JOIN') {
77
+ issues.push(
78
+ ...collectMappingIssues(config.outputMapping, ['steps', stepIndex, 'config', 'outputMapping'], message),
79
+ )
80
+ }
81
+ issues.push(...collectActivityIssues(step.activities, ['steps', stepIndex, 'activities'], message))
82
+ return issues
83
+ })
84
+ : []
85
+
86
+ const transitionIssues = Array.isArray(definition.transitions)
87
+ ? definition.transitions.flatMap((transition, transitionIndex) => {
88
+ if (!isRecord(transition)) return []
89
+ return collectActivityIssues(transition.activities, [
90
+ 'transitions',
91
+ transitionIndex,
92
+ 'activities',
93
+ ], message)
94
+ })
95
+ : []
96
+
97
+ return [...stepIssues, ...transitionIssues]
98
+ }
99
+
100
+ export function mappingTargetIssuesForApi(
101
+ definition: WorkflowDefinitionData,
102
+ prefix: Array<string | number> = [],
103
+ message = UNSAFE_MAPPING_TARGET_MESSAGE,
104
+ ): DefinitionValidationIssue[] {
105
+ return collectUnsafeMappingTargetIssues(definition, message).map((issue) => ({
106
+ path: [
107
+ ...prefix,
108
+ ...issue.path.map((segment) => (typeof segment === 'number' ? segment : String(segment))),
109
+ ],
110
+ code: 'custom',
111
+ message: issue.message,
112
+ }))
113
+ }
@@ -38,6 +38,7 @@ import {
38
38
  import * as stepHandler from './step-handler'
39
39
  import { branchToken, mergeTokenContext, type ExecutionToken } from './execution-token'
40
40
  import { WORKFLOW_ERROR_CONTEXT_KEY, buildErrorContextEntry } from './error-routing'
41
+ import { isSafeMappingTargetPath, safeGetNestedValue } from './safe-mapping-path'
41
42
 
42
43
  export interface AdvanceBranchesResult {
43
44
  outcome: 'joined' | 'waiting' | 'failed'
@@ -110,6 +111,17 @@ interface ParallelContext {
110
111
  userId?: string
111
112
  }
112
113
 
114
+ export function applyParallelJoinOutputMapping(
115
+ context: Record<string, unknown>,
116
+ outputMapping: Record<string, string>,
117
+ ): void {
118
+ for (const [topKey, sourcePath] of Object.entries(outputMapping)) {
119
+ if (topKey === 'branches' || !isSafeMappingTargetPath(topKey)) continue
120
+ const value = safeGetNestedValue(context, sourcePath)
121
+ if (value !== undefined) context[topKey] = value
122
+ }
123
+ }
124
+
113
125
  function normalizeWorkflowUserId(value: unknown): string | null {
114
126
  if (typeof value !== 'string') return null
115
127
  const trimmed = value.trim()
@@ -117,10 +129,6 @@ function normalizeWorkflowUserId(value: unknown): string | null {
117
129
  return trimmed
118
130
  }
119
131
 
120
- function getNestedValue(obj: any, path: string): any {
121
- return path.split('.').reduce((current, key) => (current == null ? undefined : current[key]), obj)
122
- }
123
-
124
132
  /**
125
133
  * Open a PARALLEL_FORK: create one ACTIVE branch per outgoing `auto` transition
126
134
  * and mark the instance FORKED. Branch tokens start positioned ON the fork step
@@ -634,11 +642,7 @@ async function fireJoin(
634
642
  // mapping cannot clobber the per-branch namespaces.
635
643
  const outputMapping: Record<string, string> | undefined = joinStep?.config?.outputMapping
636
644
  if (outputMapping) {
637
- for (const [topKey, sourcePath] of Object.entries(outputMapping)) {
638
- if (topKey === 'branches') continue
639
- const value = getNestedValue(nextContext, sourcePath)
640
- if (value !== undefined) nextContext[topKey] = value
641
- }
645
+ applyParallelJoinOutputMapping(nextContext, outputMapping)
642
646
  }
643
647
 
644
648
  // Park the root token ON the JOIN step (not its successor) and let the
@@ -0,0 +1,44 @@
1
+ const FORBIDDEN_MAPPING_PATH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype'])
2
+
3
+ export function isSafeMappingTargetPath(path: string): boolean {
4
+ return !path.split('.').some((segment) => FORBIDDEN_MAPPING_PATH_SEGMENTS.has(segment))
5
+ }
6
+
7
+ function isObjectRecord(value: unknown): value is Record<string, unknown> {
8
+ return typeof value === 'object' && value !== null
9
+ }
10
+
11
+ export function safeGetNestedValue(source: unknown, path: string): unknown {
12
+ return path.split('.').reduce<unknown>((current, segment) => {
13
+ if (!isObjectRecord(current) || !Object.hasOwn(current, segment)) return undefined
14
+ return current[segment]
15
+ }, source)
16
+ }
17
+
18
+ function cloneMappingContainer(value: unknown): Record<string, unknown> {
19
+ if (Array.isArray(value)) return [...value] as unknown as Record<string, unknown>
20
+ if (isObjectRecord(value)) return { ...value }
21
+ return {}
22
+ }
23
+
24
+ export function safeSetNestedValue(
25
+ target: Record<string, unknown>,
26
+ path: string,
27
+ value: unknown,
28
+ ): boolean {
29
+ const segments = path.split('.')
30
+ if (!isSafeMappingTargetPath(path)) return false
31
+
32
+ const lastSegment = segments.pop()
33
+ if (lastSegment === undefined) return false
34
+
35
+ let current = target
36
+ for (const segment of segments) {
37
+ const existing = Object.hasOwn(current, segment) ? current[segment] : undefined
38
+ current[segment] = cloneMappingContainer(existing)
39
+ current = current[segment] as Record<string, unknown>
40
+ }
41
+
42
+ current[lastSegment] = value
43
+ return true
44
+ }
@@ -54,6 +54,7 @@ import { createLogger } from '@open-mercato/shared/lib/logger'
54
54
  import { findWorkflowDefinition } from './find-definition'
55
55
  import { validateAgainstPorts } from './port-contract'
56
56
  import type { WorkflowIoContract } from '../data/validators'
57
+ import { safeGetNestedValue, safeSetNestedValue } from './safe-mapping-path'
57
58
 
58
59
  const logger = createLogger('workflows')
59
60
 
@@ -1678,34 +1679,6 @@ async function logStepEvent(
1678
1679
  return workflowEvent
1679
1680
  }
1680
1681
 
1681
- /**
1682
- * Get nested value from object using dot notation
1683
- *
1684
- * @param obj - Source object
1685
- * @param path - Dot-notation path (e.g., "user.email")
1686
- * @returns Value at path or undefined
1687
- */
1688
- function getNestedValue(obj: any, path: string): any {
1689
- return path.split('.').reduce((current, key) => current?.[key], obj)
1690
- }
1691
-
1692
- /**
1693
- * Set nested value in object using dot notation
1694
- *
1695
- * @param obj - Target object
1696
- * @param path - Dot-notation path (e.g., "user.email")
1697
- * @param value - Value to set
1698
- */
1699
- function setNestedValue(obj: any, path: string, value: any): void {
1700
- const keys = path.split('.')
1701
- const lastKey = keys.pop()!
1702
- const target = keys.reduce((current, key) => {
1703
- if (!(key in current)) current[key] = {}
1704
- return current[key]
1705
- }, obj)
1706
- target[lastKey] = value
1707
- }
1708
-
1709
1682
  /**
1710
1683
  * Map data from source context using mapping configuration
1711
1684
  *
@@ -1720,9 +1693,9 @@ function mapInputData(
1720
1693
  const result: Record<string, any> = {}
1721
1694
 
1722
1695
  for (const [targetKey, sourcePath] of Object.entries(mapping)) {
1723
- const value = getNestedValue(sourceContext, sourcePath)
1696
+ const value = safeGetNestedValue(sourceContext, sourcePath)
1724
1697
  if (value !== undefined) {
1725
- setNestedValue(result, targetKey, value)
1698
+ safeSetNestedValue(result, targetKey, value)
1726
1699
  }
1727
1700
  }
1728
1701
 
@@ -1744,9 +1717,9 @@ function mapOutputData(
1744
1717
  const result: Record<string, any> = {}
1745
1718
 
1746
1719
  for (const [targetKey, sourcePath] of Object.entries(mapping)) {
1747
- const value = getNestedValue(childContext, sourcePath)
1720
+ const value = safeGetNestedValue(childContext, sourcePath)
1748
1721
  if (value !== undefined) {
1749
- setNestedValue(result, targetKey, value)
1722
+ safeSetNestedValue(result, targetKey, value)
1750
1723
  }
1751
1724
  }
1752
1725