Using "dataValue('path')" inside a custom function

@Levi , I’m sorry that I missed this pull request (please keep @amber CCed on this forum if you have questions!) but I’m glad you merged it.

Check out how sourceValue and dataValue work in language-common. There are some interesting differences between the two (namely that dataValue is a shortcut to $.data.yourPath (since that’s such a common place to select values) and sourceValue is just $.yourPath.

Big important thing

But the big important thing is that these are both functions! OpenFn does do a little bit of magic :magic_wand: to make all these asynchronous bits (like HTTP requests) behave like a big, predictable, synchronous pipeline. One bit of magic is that each operation in a job will be called after the previous operation completes. Another bit of magic is that each function used inside an operation (except for a custom function that takes state, like an fn(input => output) block) will be called when that operation runs thanks to expandReferences, rather than right at the beginning of the job… this is super important because it means that if you write

create('patient', { name: dataValue('full_name') })
create('visit', { patientId: dataValue('response.id') })

that second operation will be evaluating state.data.response.id after the first operation is finished. In other words, state.data.full_name is from the initial state, but then state.data.response.id is from the state when that second operation gets run. In my example, I’m assuming that the first operation gets a response from some FHIR server with a new patient ID that we can use in later operations.

Now, imagine that you wrote:

create('patient', { name: state.data.full_name })
create('visit', { patientId: state.data.response.id })

Since neither state.data.full_name nor state.data.response.id are functions, they’ll be evaluated right at the start of the job’s execution… so that patientId we want to use in the second operation will be blank! :person_facepalming:

Note that dataValue('response.id') will work in there, and so will state => state.data.response.id since both are FUNCTIONS which can be called with state.

Finally… getting back to your pull request: if you write:

fn(state => {
  console.log(
    dataValue('path')
  )
})

You’ll see that you’re logging a function… not what that function returns when called with state! As you point out, when you’re inside that custom function (either inside your own state => { whatever } function or inside a fn(state => { whatever }) operation, if you want to see the result of calling dataValue with state, you need to do it yourself… we don’t “mess” with your code inside those custom Javascript playgrounds:

fn(state => {
  console.log(
    dataValue('path')(state) // I call dataValue('path') with state myself!
  )
})

A final thought… since you’re already inside a custom function that takes state when you create those FHIR resources in your fn(state => blah) block, you might prefer accessing state.data.path directly… since it’s already inside a custom function, you don’t have to worry about it being evaluated right at the start of the job… it will wait to be called until the previous operation has finished. You might then write:

// Build "Patient" resource
fn(state => {
  // take the body of the triggering message and call it "input", for example:
  const input = state.data 
  // then use `input['x']` everywhere else...

  const patient = {
    fullUrl: 'urn:uuid:0fc374a1-a226-4552-9683-55dd510e67c9', // will be referenced in many `Observation` resources
    request: {
      method: 'PUT',
      url: `Patient?identifier=https://fhir.kemkes.go.id/id/nik|${input['patient_ID/patient_identifier_NIK'].replace(/ /g, "_")}`
    },
    resource: {
      resourceType: 'Patient',
      identifier: [
        {
          use: 'usual',
          system: 'https://fhir.kemkes.go.id/id/nik',
          value: input['patient_ID/patient_identifier_NIK'].replace(/ /g, "_"),
        },
      ],
      name: [
        {
          use: 'official',
          text: input['patient_ID/patient_name'],
          text: input['patient_ID/patient_name'],
        },
      ],
  })...

I hope this provides some helpful context.

Do you think you’re ready to start working on the FHIR adaptor? I’d love to take these custom functions that you’ve written in your job and abstract them into helpers provided by language-fhir so that all of this structure is provided out of the box to others building FHIR resources and requests!

1 Like

@taylordowns2000 Thank you so much for the very clear explanation! :smile_cat:

Sure. I’d help with all I got so that every “not yet FHIR-compliant” data in the world can be transformed into FHIR-compliant data and achieve interoperability with low cost, thus improving the quality of care given to whoever in need.