@@ -286,6 +286,47 @@ async function evaluateResolvedCondition(
286286 }
287287}
288288
289+ function createOffloadEnv ( language : string , producerOutput : Record < string , unknown > ) {
290+ const { block, ctx } = createResolver ( language )
291+ const producer = createBlock ( 'producer' , 'Producer' , BlockType . API )
292+ const state = new ExecutionState ( )
293+ state . setBlockOutput ( 'producer' , producerOutput )
294+ const workflow : SerializedWorkflow = {
295+ version : '1' ,
296+ blocks : [ producer , block ] ,
297+ connections : [ ] ,
298+ loops : { } ,
299+ parallels : { } ,
300+ }
301+ const resolver = new VariableResolver ( workflow , { } , state )
302+ const durableCtx = {
303+ ...ctx ,
304+ blockStates : state . getBlockStates ( ) ,
305+ workspaceId : 'workspace-1' ,
306+ workflowId : 'workflow-1' ,
307+ executionId : 'execution-1' ,
308+ largeValueKeys : [ ] as string [ ] ,
309+ } as ExecutionContext
310+ return { block, resolver, durableCtx }
311+ }
312+
313+ function largeRef ( size : number ) {
314+ return {
315+ __simLargeValueRef : true ,
316+ version : 1 ,
317+ id : 'lv_ABCDEFGHIJKL' ,
318+ kind : 'string' ,
319+ size,
320+ key : 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json' ,
321+ executionId : 'execution-1' ,
322+ }
323+ }
324+
325+ function resolveAgainstProducer ( language : string , output : Record < string , unknown > , code : string ) {
326+ const { block, resolver, durableCtx } = createOffloadEnv ( language , output )
327+ return resolver . resolveInputsForFunctionBlock ( durableCtx , 'function' , { code } , block )
328+ }
329+
289330describe ( 'VariableResolver function block inputs' , ( ) => {
290331 it ( 'inlines only structurally inert condition literals and defers the rest to the compiler' , async ( ) => {
291332 const { ctx, resolver } = createResolver ( )
@@ -924,116 +965,100 @@ describe('VariableResolver function block inputs', () => {
924965 } )
925966
926967 it ( 'fails whole large value refs for Function runtimes without lazy helpers' , async ( ) => {
927- const { block, ctx } = createResolver ( 'python' )
928- const state = new ExecutionState ( )
929- state . setBlockOutput ( 'producer' , {
930- result : {
931- __simLargeValueRef : true ,
932- version : 1 ,
933- id : 'lv_ABCDEFGHIJKL' ,
934- kind : 'object' ,
935- size : 12 * 1024 * 1024 ,
936- key : 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json' ,
937- executionId : 'execution-1' ,
938- } ,
939- } )
940- const workflow : SerializedWorkflow = {
941- version : '1' ,
942- blocks : [ createBlock ( 'producer' , 'Producer' , BlockType . API ) , block ] ,
943- connections : [ ] ,
944- loops : { } ,
945- parallels : { } ,
946- }
947- const largeResolver = new VariableResolver ( workflow , { } , state )
948- const largeCtx = {
949- ...ctx ,
950- blockStates : state . getBlockStates ( ) ,
951- } as ExecutionContext
952-
953968 await expect (
954- largeResolver . resolveInputsForFunctionBlock (
955- largeCtx ,
956- 'function' ,
957- { code : 'return <Producer.result>' } ,
958- block
969+ resolveAgainstProducer (
970+ 'python' ,
971+ { result : largeRef ( 12 * 1024 * 1024 ) } ,
972+ 'return <Producer.result>'
959973 )
960- ) . rejects . toThrow ( 'This execution value is too large to inline' )
974+ ) . rejects . toThrow (
975+ '<Producer.result> is too large to pass inline (12.0 MB). Select a smaller field, or read it in JavaScript without imports.'
976+ )
961977 } )
962978
963979 it ( 'fails whole large value refs for JavaScript with imports' , async ( ) => {
964- const { block, ctx } = createResolver ( 'javascript' )
965- const state = new ExecutionState ( )
966- state . setBlockOutput ( 'producer' , {
967- result : {
968- __simLargeValueRef : true ,
969- version : 1 ,
970- id : 'lv_ABCDEFGHIJKL' ,
971- kind : 'object' ,
972- size : 12 * 1024 * 1024 ,
973- key : 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json' ,
974- executionId : 'execution-1' ,
975- } ,
976- } )
977- const workflow : SerializedWorkflow = {
978- version : '1' ,
979- blocks : [ createBlock ( 'producer' , 'Producer' , BlockType . API ) , block ] ,
980- connections : [ ] ,
981- loops : { } ,
982- parallels : { } ,
983- }
984- const largeResolver = new VariableResolver ( workflow , { } , state )
985- const largeCtx = {
986- ...ctx ,
987- blockStates : state . getBlockStates ( ) ,
988- } as ExecutionContext
989-
990980 await expect (
991- largeResolver . resolveInputsForFunctionBlock (
992- largeCtx ,
993- 'function' ,
994- { code : "import x from 'x'\nreturn <Producer.result>" } ,
995- block
981+ resolveAgainstProducer (
982+ 'javascript' ,
983+ { result : largeRef ( 12 * 1024 * 1024 ) } ,
984+ "import x from 'x'\nreturn <Producer.result>"
996985 )
997- ) . rejects . toThrow ( 'This execution value is too large to inline' )
986+ ) . rejects . toThrow (
987+ '<Producer.result> is too large to pass inline (12.0 MB). Select a smaller field, or read it in JavaScript without imports.'
988+ )
998989 } )
999990
1000- it ( 'fails nested large value refs for JavaScript instead of leaking ref markers' , async ( ) => {
1001- const { block, ctx } = createResolver ( 'javascript' )
1002- const state = new ExecutionState ( )
1003- state . setBlockOutput ( 'producer' , {
1004- result : {
1005- rows : {
1006- __simLargeValueRef : true ,
1007- version : 1 ,
1008- id : 'lv_ABCDEFGHIJKL' ,
1009- kind : 'array' ,
1010- size : 12 * 1024 * 1024 ,
1011- key : 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json' ,
1012- executionId : 'execution-1' ,
1013- } ,
1014- } ,
991+ describe ( 'nested large values' , ( ) => {
992+ it ( 'names the nested field instead of leaking ref markers' , async ( ) => {
993+ await expect (
994+ resolveAgainstProducer (
995+ 'javascript' ,
996+ { result : { rows : largeRef ( 12 * 1024 * 1024 ) } } ,
997+ 'return <Producer.result>.rows.length'
998+ )
999+ ) . rejects . toThrow (
1000+ '<Producer.result> contains a 12.0 MB value at <Producer.result.rows>. Reference that field directly.'
1001+ )
10151002 } )
1016- const workflow : SerializedWorkflow = {
1017- version : '1' ,
1018- blocks : [ createBlock ( 'producer' , 'Producer' , BlockType . API ) , block ] ,
1019- connections : [ ] ,
1020- loops : { } ,
1021- parallels : { } ,
1022- }
1023- const largeResolver = new VariableResolver ( workflow , { } , state )
1024- const largeCtx = {
1025- ...ctx ,
1026- blockStates : state . getBlockStates ( ) ,
1027- } as ExecutionContext
10281003
1029- await expect (
1030- largeResolver . resolveInputsForFunctionBlock (
1031- largeCtx ,
1032- 'function' ,
1033- { code : 'return <Producer.result>.rows.length' } ,
1034- block
1004+ it ( 'names the array index of a large value nested in an array' , async ( ) => {
1005+ await expect (
1006+ resolveAgainstProducer (
1007+ 'javascript' ,
1008+ { contents : [ largeRef ( 10.5 * 1024 * 1024 ) ] } ,
1009+ 'return <Producer.contents>[0].slice(0, 10)'
1010+ )
1011+ ) . rejects . toThrow (
1012+ '<Producer.contents> contains a 10.5 MB value at <Producer.contents[0]>. Reference that field directly.'
10351013 )
1036- ) . rejects . toThrow ( 'This execution value contains nested large values' )
1014+ } )
1015+
1016+ it ( 'names a nested array manifest without descending into its chunks' , async ( ) => {
1017+ const chunk = largeRef ( 9 * 1024 * 1024 )
1018+ const manifest = {
1019+ __simLargeArrayManifest : true ,
1020+ version : 2 ,
1021+ kind : 'array' ,
1022+ totalCount : 2 ,
1023+ chunkCount : 1 ,
1024+ byteSize : chunk . size ,
1025+ chunks : [ { ref : chunk , count : 2 , byteSize : chunk . size } ] ,
1026+ preview : [ ] ,
1027+ }
1028+ await expect (
1029+ resolveAgainstProducer (
1030+ 'javascript' ,
1031+ { result : { items : manifest } } ,
1032+ 'return <Producer.result>.items.length'
1033+ )
1034+ ) . rejects . toThrow (
1035+ '<Producer.result> contains a 9.0 MB value at <Producer.result.items>. Reference that field directly.'
1036+ )
1037+ } )
1038+
1039+ it ( 'does not suggest a direct reference where it could not be loaded either' , async ( ) => {
1040+ await expect (
1041+ resolveAgainstProducer (
1042+ 'python' ,
1043+ { contents : [ largeRef ( 10.5 * 1024 * 1024 ) ] } ,
1044+ 'return <Producer.contents>'
1045+ )
1046+ ) . rejects . toThrow (
1047+ '<Producer.contents> contains a 10.5 MB value at <Producer.contents[0]>. Select a smaller field, or read it in JavaScript without imports.'
1048+ )
1049+ } )
1050+
1051+ it ( 'omits a field reference that a key cannot be written as' , async ( ) => {
1052+ await expect (
1053+ resolveAgainstProducer (
1054+ 'javascript' ,
1055+ { result : { 'report.txt' : largeRef ( 10.5 * 1024 * 1024 ) } } ,
1056+ 'return <Producer.result>'
1057+ )
1058+ ) . rejects . toThrow (
1059+ '<Producer.result> contains a 10.5 MB value. Reference the field you need directly.'
1060+ )
1061+ } )
10371062 } )
10381063
10391064 it ( 'breaks JavaScript string literals around quoted block references' , async ( ) => {
@@ -1148,30 +1173,6 @@ describe('VariableResolver function block inputs', () => {
11481173describe ( 'VariableResolver function context overflow offload' , ( ) => {
11491174 const REF_KEY = 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json'
11501175
1151- function createOffloadEnv ( language : string , producerOutput : Record < string , unknown > ) {
1152- const { block, ctx } = createResolver ( language )
1153- const producer = createBlock ( 'producer' , 'Producer' , BlockType . API )
1154- const state = new ExecutionState ( )
1155- state . setBlockOutput ( 'producer' , producerOutput )
1156- const workflow : SerializedWorkflow = {
1157- version : '1' ,
1158- blocks : [ producer , block ] ,
1159- connections : [ ] ,
1160- loops : { } ,
1161- parallels : { } ,
1162- }
1163- const resolver = new VariableResolver ( workflow , { } , state )
1164- const durableCtx = {
1165- ...ctx ,
1166- blockStates : state . getBlockStates ( ) ,
1167- workspaceId : 'workspace-1' ,
1168- workflowId : 'workflow-1' ,
1169- executionId : 'execution-1' ,
1170- largeValueKeys : [ ] as string [ ] ,
1171- } as ExecutionContext
1172- return { block, resolver, durableCtx }
1173- }
1174-
11751176 beforeEach ( ( ) => {
11761177 mockStoreLargeValue . mockReset ( )
11771178 mockStoreLargeValue . mockResolvedValue ( {
0 commit comments