@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 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!
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!