From 78643f7c0f632bd08a9b541a1dca39b389a803bb Mon Sep 17 00:00:00 2001 From: Hunter Achieng Date: Wed, 17 Jun 2026 16:28:04 +0300 Subject: [PATCH 1/2] fix: update job writing guide Signed-off-by: Hunter Achieng --- docs/jobs/best-practices.md | 9 +- docs/jobs/data-transformation.md | 12 +- docs/jobs/javascript.md | 11 +- docs/jobs/job-examples.md | 480 +++++++++++++------------------ docs/jobs/job-snippets.md | 87 +++--- docs/jobs/operations.md | 24 +- docs/jobs/state.md | 2 +- docs/jobs/using-cursors.md | 10 +- 8 files changed, 274 insertions(+), 361 deletions(-) diff --git a/docs/jobs/best-practices.md b/docs/jobs/best-practices.md index 831038e04be0..05a38a052a01 100644 --- a/docs/jobs/best-practices.md +++ b/docs/jobs/best-practices.md @@ -11,13 +11,14 @@ password from your `state.configuration` (or "credential" if using the app) into http request body. ```js -post('/api/v1/auth/login', { - body: { +post( + '/api/v1/auth/login', + { username: $.configuration.username, //map the UN from credential password: $.configuration.password, //map the PW from credential }, - headers: { 'content-type': 'application/json' }, -}); + { headers: { 'content-type': 'application/json' } } +); ``` > **Note:** While most adaptors handles authentication automatically, The diff --git a/docs/jobs/data-transformation.md b/docs/jobs/data-transformation.md index d10f21e29dcc..74f430216c59 100644 --- a/docs/jobs/data-transformation.md +++ b/docs/jobs/data-transformation.md @@ -22,7 +22,7 @@ fn(state => { // Read the data we fetched const obj = state.data; - // convert it by mapping properties from one object to the o ther + // convert it by mapping properties from one object to the other state.uploadData = { id: obj.id, name: `${obj.first_name} ${obj.last_name}`, @@ -106,7 +106,7 @@ fn((state) => { state.transformed = [] return state; }) -each("$.items[*]", fn(state) => { +each("$.items[*]", fn(state => { // Pull the next item off the state const next = state.data; @@ -118,7 +118,7 @@ each("$.items[*]", fn(state) => { // Always return state return state; -}) +})) ``` Or we can pass in another operation, like this Salesforce example: @@ -127,9 +127,9 @@ Or we can pass in another operation, like this Salesforce example: each( '$.form.participants[*]', upsert('Person__c', 'Participant_PID__c', state => ({ - Participant_PID__c: state.pid, - First_Name__c: state.participant_first_name, - Surname__c: state.participant_surname, + Participant_PID__c: state.data.pid, + First_Name__c: state.data.participant_first_name, + Surname__c: state.data.participant_surname, })) ); ``` diff --git a/docs/jobs/javascript.md b/docs/jobs/javascript.md index 136170e438a2..de2e1630c86c 100644 --- a/docs/jobs/javascript.md +++ b/docs/jobs/javascript.md @@ -231,7 +231,7 @@ declare more properties: ```js const newState = { - ...state + ...state, data: {} // create a new data object but keep all other keys of state } ``` @@ -243,7 +243,7 @@ finally overwrites the data key. ```js const newState = { ...defaults, - ...state + ...state, data: {} // create a new data object but keep all other keys of state } ``` @@ -275,7 +275,7 @@ const b = { b.x = 2; // a.x is unchanged b.y.values = []; // a.y.values is changed -b.y = 20' // a.y is unchanged +b.y = 20; // a.y is unchanged ``` A deep clone means that all properties in the whole object tree are cloned. @@ -305,7 +305,7 @@ fn(state => { // Here we build the payload of our http request body... // We assume the input is an array of records const payload = state.data.map(record => ({ - location: locationMap[record.location_id] //translate location_id to the mapped value + location: locationMap[record.location_id], //translate location_id to the mapped value external_id: record.case_id })); @@ -314,11 +314,10 @@ fn(state => { //Workflow step 2 //Then we post the payload built in the prior operation to create a record -post('/api/myEndpoint', { +post('/api/myEndpoint', state => state.payload, { headers: { 'Content-Type': 'application/json', }, - body: (state) => state.payload }); ``` diff --git a/docs/jobs/job-examples.md b/docs/jobs/job-examples.md index 34b9dc97309e..99d4cebf9b04 100644 --- a/docs/jobs/job-examples.md +++ b/docs/jobs/job-examples.md @@ -29,26 +29,20 @@ receipt to upsert a `Patient__c` record in Salesforce and create multiple new `Patient_Visit__c` (child to Patient) records. ```js -upsert( - 'Patient__c', - 'Patient_Id__c', - fields( - field('Patient_Id__c', dataValue('form.patient_ID')), - relationship('Nurse__r', 'Nurse_ID_code__c', dataValue('form.staff_id')), - field('Phone_Number__c', dataValue('form.mobile_phone')) - ) -), - each( - join('$.data.form.visits[*]', '$.references[0].id', 'Id'), - create( - 'Visit__c', - fields( - field('Patient__c', dataValue('Id')), - field('Date__c', dataValue('date')), - field('Reason__c', dataValue('why_did_they_see_doctor')) - ) - ) - ); +upsert('Patient__c', 'Patient_Id__c', state => ({ + Patient_Id__c: state.data.form.patient_ID, + Nurse__r: { Nurse_ID_code__c: state.data.form.staff_id }, + Phone_Number__c: state.data.form.mobile_phone, +})); + +each( + '$.data.form.visits[*]', + create('Visit__c', state => ({ + Patient__c: lastReferenceValue('id')(state), + Date__c: state.data.date, + Reason__c: state.data.why_did_they_see_doctor, + })) +); ``` ### Accessing the "data array" in Open Data Kit submissions @@ -59,15 +53,12 @@ ODK. ```js each( '$.data.data[*]', - create( - 'ODK_Submission__c', - fields( - field('Site_School_ID_Number__c', dataValue('school')), - field('Date_Completed__c', dataValue('date')), - field('comments__c', dataValue('comments')), - field('ODK_Key__c', dataValue('*meta-instance-id*')) - ) - ) + create('ODK_Submission__c', state => ({ + Site_School_ID_Number__c: state.data.school, + Date_Completed__c: state.data.date, + comments__c: state.data.comments, + ODK_Key__c: state.data['*meta-instance-id*'], + })) ); ``` @@ -77,109 +68,73 @@ Here, the user brings `time_end` and `parentId` onto the line items from the parent object. ```js -each( - dataPath('data[*]'), - combine( - create( - 'transaction__c', - fields( - field('Transaction_Date__c', dataValue('today')), - relationship( - 'Person_Responsible__r', - 'Staff_ID_Code__c', - dataValue('person_code') - ), - field('metainstanceid__c', dataValue('*meta-instance-id*')) - ) - ), - each( - merge( - dataPath('line_items[*]'), - fields( - field('end', dataValue('time_end')), - field('parentId', lastReferenceValue('id')) - ) - ), - create( - 'line_item__c', - fields( - field('transaction__c', dataValue('parentId')), - field('Barcode__c', dataValue('product_barcode')), - field('ODK_Form_Completed__c', dataValue('end')) - ) - ) - ) - ) -); -``` +each('$.data[*]', fn(async state => { + const record = state.data; + + // Create the parent transaction record + state = await create('transaction__c', { + Transaction_Date__c: record.today, + Person_Responsible__r: { Staff_ID_Code__c: record.person_code }, + metainstanceid__c: record['*meta-instance-id*'], + })(state); + + const transactionId = lastReferenceValue('id')(state); + + // Create each child line item linked to the parent + for (const lineItem of record.line_items) { + state = await create('line_item__c', { + transaction__c: transactionId, + Barcode__c: lineItem.product_barcode, + ODK_Form_Completed__c: record.time_end, + })(state); + } -> **NB - there was a known bug with the `combine` function which has been -> resolved. `combine` can be used to combine two operations into one and is -> commonly used to run multiple `create`'s inside an `each(path, operation)`. -> The source code for combine can be found here: -> [language-common: combine](https://github.com/OpenFn/language-common/blob/master/src/index.js#L204-L222)** + return state; +})); +``` ### Create many child records WITHOUT a repeat group in ODK ```js -beta.each( +each( + '$.data.data[*]', + upsert('Outlet__c', 'Outlet_Code__c', state => ({ + Outlet_Code__c: state.data.outlet_code, + Location__Latitude__s: state.data['gps:Latitude'], + Location__Longitude__s: state.data['gps:Longitude'], + })) +); + +each( '$.data.data[*]', - upsert( - 'Outlet__c', - 'Outlet_Code__c', - fields( - field('Outlet_Code__c', dataValue('outlet_code')), - field('Location__Latitude__s', dataValue('gps:Latitude')), - field('Location__Longitude__s', dataValue('gps:Longitude')) - ) - ) -), - beta.each( - '$.data.data[*]', - upsert( - 'Outlet_Call__c', - 'Invoice_Number__c', - fields( - field('Invoice_Number__c', dataValue('invoice_number')), - relationship('Outlet__r', 'Outlet_Code__c', dataValue('outlet_code')), - relationship('RecordType', 'name', 'No Call Card'), - field('Trip__c', 'a0FN0000008jPue'), - relationship( - 'Sales_Person__r', - 'Sales_Rep_Code__c', - dataValue('sales_rep_code') - ), - field('Date__c', dataValue('date')), - field('Comments__c', dataValue('comments')) - ) - ) - ); + upsert('Outlet_Call__c', 'Invoice_Number__c', state => ({ + Invoice_Number__c: state.data.invoice_number, + Outlet__r: { Outlet_Code__c: state.data.outlet_code }, + RecordType: { name: 'No Call Card' }, + Trip__c: 'a0FN0000008jPue', + Sales_Person__r: { Sales_Rep_Code__c: state.data.sales_rep_code }, + Date__c: state.data.date, + Comments__c: state.data.comments, + })) +); ``` ### Salesforce: perform an update ```js -update("Patient__c", fields( - field("Id", dataValue("pathToSalesforceId")), - field("Name__c", dataValue("patient.first_name")), - field(...) -)); +update('Patient__c', state => ({ + Id: state.data.pathToSalesforceId, + Name__c: state.data.patient.first_name, +})); ``` -### Salesforce: Set record type using 'relationship(...)' +### Salesforce: Set record type using a relationship ```js -create( - 'custom_obj__c', - fields( - relationship( - 'RecordType', - 'name', - dataValue('submission_type'), - field('name', dataValue('Name')) - ) - ) -); +create('custom_obj__c', state => ({ + RecordType: { name: state.data.submission_type }, + Name: state.data.Name, +})); ``` ### Salesforce: Set record type using record Type ID @@ -187,168 +142,132 @@ create( ```js each( '$.data.data[*]', - create( - 'fancy_object__c', - fields( - field('RecordTypeId', '012110000008s19'), - field('site_size', dataValue('size')) - ) - ) + create('fancy_object__c', state => ({ + RecordTypeId: '012110000008s19', + site_size: state.data.size, + })) ); ``` -### Telerivet: Send SMS based on Salesforce workflow alert +### HTTP: Send SMS via Telerivet based on Salesforce workflow alert ```js -send( - fields( - field( - 'to_number', - dataValue( - 'Envelope.Body.notifications.Notification.sObject.phone_number__c' - ) - ), - field('message_type', 'sms'), - field('route_id', ''), - field('content', function (state) { - return 'Hey there. Your name is '.concat( - dataValue('Envelope.Body.notifications.Notification.sObject.name__c')( - state - ), - '.' - ); - }) - ) -); +post('/messages/send', state => ({ + to_number: + state.data.Envelope.Body.notifications.Notification.sObject + .phone_number__c, + message_type: 'sms', + route_id: '', + content: `Hey there. Your name is ${ + state.data.Envelope.Body.notifications.Notification.sObject.name__c + }.`, +})); ``` -### HTTP: fetch but don't fail! +### HTTP: post but don't fail! ```js -// ============= -// We use "fetchWithErrors(...)" so that when the -// SMS gateway returns an error the run does not "fail". -// It "succeeds" and then delivers that error message -// back to Salesforce with the "Update SMS Status" job. -// ============= -fetchWithErrors({ - getEndpoint: 'send_to_contact', - query: function (state) { - return { - msisdn: - state.data.Envelope.Body.notifications.Notification.sObject - .SMS__Phone_Number__c, - message: - state.data.Envelope.Body.notifications.Notification.sObject - .SMS__Message__c, - api_key: 'some-secret-key', - }; - }, - externalId: state.data.Envelope.Body.notifications.Notification.sObject.Id, - postUrl: 'https://www.openfn.org/inbox/another-secret-key', +// Use .catch() so that when the SMS gateway returns an error the run does not +// "fail". The error is captured in state for the next job to handle. +post('/send_to_contact', state => ({ + msisdn: + state.data.Envelope.Body.notifications.Notification.sObject + .SMS__Phone_Number__c, + message: + state.data.Envelope.Body.notifications.Notification.sObject + .SMS__Message__c, +})).catch(err => { + console.log('SMS send failed:', err.message); + state.smsError = err.message; + return state; }); ``` ### Sample DHIS2 events API job: ```js -event( - fields( - field('program', 'eBAyeGv0exc'), - field('orgUnit', 'DiszpKrYNg8'), - field('eventDate', dataValue('properties.date')), - field('status', 'COMPLETED'), - field('storedBy', 'admin'), - field('coordinate', { - latitude: '59.8', - longitude: '10.9', - }), - field('dataValues', function (state) { - return [ - { - dataElement: 'qrur9Dvnyt5', - value: dataValue('properties.prop_a')(state), - }, - { - dataElement: 'oZg33kd9taw', - value: dataValue('properties.prop_b')(state), - }, - { - dataElement: 'msodh3rEMJa', - value: dataValue('properties.prop_c')(state), - }, - ]; - }) - ) -); +create('events', state => ({ + program: 'eBAyeGv0exc', + orgUnit: 'DiszpKrYNg8', + occurredAt: state.data.properties.date, + status: 'COMPLETED', + coordinate: { + latitude: '59.8', + longitude: '10.9', + }, + dataValues: [ + { dataElement: 'qrur9Dvnyt5', value: state.data.properties.prop_a }, + { dataElement: 'oZg33kd9taw', value: state.data.properties.prop_b }, + { dataElement: 'msodh3rEMJa', value: state.data.properties.prop_c }, + ], +})); ``` ### Sample DHIS2 data value sets API job: ```js -dataValueSet( - fields( - field('dataSet', 'pBOMPrpg1QX'), - field('orgUnit', 'DiszpKrYNg8'), - field('period', '201401'), - field('completeData', dataValue('date')), - field('dataValues', function (state) { - return [ - { dataElement: 'f7n9E0hX8qk', value: dataValue('prop_a')(state) }, - { dataElement: 'Ix2HsbDMLea', value: dataValue('prop_b')(state) }, - { dataElement: 'eY5ehpbEsB7', value: dataValue('prop_c')(state) }, - ]; - }) - ) -); +create('dataValueSets', state => ({ + dataSet: 'pBOMPrpg1QX', + orgUnit: 'DiszpKrYNg8', + period: '201401', + completeDate: state.data.date, + dataValues: [ + { dataElement: 'f7n9E0hX8qk', value: state.data.prop_a }, + { dataElement: 'Ix2HsbDMLea', value: state.data.prop_b }, + { dataElement: 'eY5ehpbEsB7', value: state.data.prop_c }, + ], +})); ``` ### sample openMRS expression, creates a person and then a patient ```js -person( - fields( - field('gender', 'F'), - field('names', function (state) { - return [ - { - givenName: dataValue('form.first_name')(state), - familyName: dataValue('form.last_name')(state), - }, - ]; - }) - ) -), - patient( - fields( - field('person', lastReferenceValue('uuid')), - field('identifiers', function (state) { - return [ - { - identifier: '1234', - identifierType: '8d79403a-c2cc-11de-8d13-0010c6dffd0f', - location: '8d6c993e-c2cc-11de-8d13-0010c6dffd0f', - preferred: true, - }, - ]; - }) - ) - ); +create('person', state => ({ + gender: 'F', + names: [ + { + givenName: state.data.form.first_name, + familyName: state.data.form.last_name, + }, + ], +})); + +create('patient', state => ({ + person: lastReferenceValue('uuid')(state), + identifiers: [ + { + identifier: '1234', + identifierType: '8d79403a-c2cc-11de-8d13-0010c6dffd0f', + location: '8d6c993e-c2cc-11de-8d13-0010c6dffd0f', + preferred: true, + }, + ], +})); ``` ### merge many values into a child path ```js +// Merge parent-level values into each child item before iterating +fn(state => { + const parentId = lastReferenceValue('id')(state); + const metaId = state.data['*meta-instance-id*']; + state.childItems = state.data.CHILD_ARRAY.map(item => ({ + ...item, + metaId, + parentId, + })); + return state; +}); + each( - merge( - dataPath("CHILD_ARRAY[*]"), - fields( - field("metaId", dataValue("*meta-instance-id*")), - field("parentId", lastReferenceValue("id")) - ) - ), - create(...) -) + '$.childItems[*]', + create('some_object__c', state => ({ + metaId: state.data.metaId, + parentId: state.data.parentId, + // ... other fields mapped from state.data + })) +); ``` ### arrayToString @@ -360,63 +279,58 @@ arrayToString(arr, separator_string); ### access an image URL from an ODK submission ```js -// In ODK the image URL is inside an image object... -field("Photo_URL_text__c", dataValue("image.url")), +// In ODK the image URL is inside an image object. +// Access it via state when mapping data: +create('Photo__c', state => ({ + Photo_URL_text__c: state.data.image.url, +})); ``` ### alterState (alter state) to make sure data is in an array ```js -// Here, we make sure CommCare gives us an array to use in each(merge(...), ...) +// Here, we make sure CommCare gives us an array and merge parent-level fields +// into each item before iterating. fn(state => { const idCards = state.data.form.ID_cards_given_to_vendor; - if (!Array.isArray(idCards)) { - state.data.form.ID_cards_given_to_vendor = [idCards]; - } + state.data.form.ID_cards_given_to_vendor = (Array.isArray(idCards) + ? idCards + : [idCards] + ).map(card => ({ + ...card, + Vendor_Id: state.data.form.ID_vendor, + form_finished_time: state.data.form.meta.timeEnd, + })); return state; }); // Now state has been changed, and we carry on... each( - merge( - dataPath('form.ID_cards_given_to_vendor[*]'), - fields( - field('Vendor_Id', dataValue('form.ID_vendor')), - field('form_finished_time', dataValue('form.meta.timeEnd')) - ) - ), - upsert( - 'Small_Packet__c', - 'sp_id__c', - fields( - field('sp_id__c', dataValue('ID_cards_given_to_vendor')), - relationship('Vendor__r', 'Badge_Code__c', dataValue('Vendor_Id')), - field( - 'Small_Packet_Distribution_Date__c', - dataValue('form_finished_time') - ) - ) - ) + '$.data.form.ID_cards_given_to_vendor[*]', + upsert('Small_Packet__c', 'sp_id__c', state => ({ + sp_id__c: state.data.ID_cards_given_to_vendor, + Vendor__r: { Badge_Code__c: state.data.Vendor_Id }, + Small_Packet_Distribution_Date__c: state.data.form_finished_time, + })) ); ``` ### Login in to a server with a custom SSL Certificate This snippet describes how you would connect to a secure server ignoring SSL -certificate verification. Set `strictSSL: false` in the options argument of the -`post` function in `language-http`. +certificate verification. Pass `tls: { rejectUnauthorized: false }` in the +options argument of the `post` function in `@openfn/language-http`. ```js post( - `${state.configuration.url}/${path}`, + `${$.configuration.url}/${path}`, { - headers: { 'content-type': 'application/json' }, - body: { - email: 'Luka', - password: 'somethingSecret', - }, - strictSSL: false, + email: 'Luka', + password: 'somethingSecret', }, - callback + { + headers: { 'content-type': 'application/json' }, + tls: { rejectUnauthorized: false }, + } ); ``` diff --git a/docs/jobs/job-snippets.md b/docs/jobs/job-snippets.md index 45bf7a84c28b..92673ef2b494 100644 --- a/docs/jobs/job-snippets.md +++ b/docs/jobs/job-snippets.md @@ -6,46 +6,41 @@ sidebar_label: Code Snippets This section includes a number of useful JavaScript code snippets which you can use in your jobs. -Most snippets are implemented as callbacks to other operations. - -You can copy these callbacks and adapt them to suit your own code. +Most snippets are written as `fn()` blocks or inline arrow functions. Copy and +adapt them to suit your own code. ## General ### Custom replacer ```js -field('destination__c', state => { - return dataValue('path_to_data')(state).toString().replace('cats', 'dogs'); +fn(state => { + state.destination__c = state.data.path_to_data.toString().replace(/cats/g, 'dogs'); + return state; }); ``` -This will replace all "cats" with "dogs" in the string that lives at -`path_to_data`. - -> **NOTE:** The JavaScript `replace()` function only replaces the first instance -> of whatever argument you specify. If you're looking for a way to replace all -> instances, we suggest you use a regex like we did in the -> [example](#concatenation-of-null-values) below. +This will replace all occurrences of "cats" with "dogs" in the string that lives at +`path_to_data`. The regex flag `g` ensures every instance is replaced, not just the first. ### Custom arrayToString ```js -field("target_specie_list__c", function(state) { - return Array.apply( - null, sourceValue("$.data.target_specie_list")(state) - ).join(', ') -}), +fn(state => { + state.target_specie_list__c = state.data.target_specie_list.join(', '); + return state; +}); ``` -It will take an array, and concatenate each item into a string with a ", " +It will take an array, and concatenate each item into a string with a `", "` separator. ### Custom concatenation ```js -field('ODK_Key__c', function (state) { - return dataValue('metaId')(state).concat('(', dataValue('index')(state), ')'); +fn(state => { + state.ODK_Key__c = `${state.data.metaId}(${state.data.index})`; + return state; }); ``` @@ -57,15 +52,21 @@ This will concatenate many values, even if one or more are null, writing them to a field called Main_Office_City_c. ```js -... - field("Main_Office_City__c", function(state) { - return arrayToString([ - dataValue("Main_Office_City_a")(state) === null ? "" : dataValue("Main_Office_City_a")(state).toString().replace(/-/g, " "), - dataValue("Main_Office_City_b")(state) === null ? "" : dataValue("Main_Office_City_b")(state).toString().replace(/-/g, " "), - dataValue("Main_Office_City_c")(state) === null ? "" : dataValue("Main_Office_City_c")(state).toString().replace(/-/g, " "), - dataValue("Main_Office_City_d")(state) === null ? "" : dataValue("Main_Office_City_d")(state).toString().replace(/-/g, " "), - ].filter(Boolean), ',') - }) +fn(state => { + const clean = val => + val == null ? '' : val.toString().replace(/-/g, ' '); + + state.Main_Office_City__c = [ + clean(state.data.Main_Office_City_a), + clean(state.data.Main_Office_City_b), + clean(state.data.Main_Office_City_c), + clean(state.data.Main_Office_City_d), + ] + .filter(Boolean) + .join(','); + + return state; +}); ``` > Notice how this custom function makes use of the **regex** `/-/g` to ensure @@ -77,22 +78,25 @@ If you ever want to retrieve the FIRST object you created, or the SECOND, or the Nth, for that matter, a function like this will do the trick. ```js -field('parent__c', function (state) { - return state.references[state.references.length - 1].id; +fn(state => { + // length - 1 = last created, length - 2 = second-to-last, etc. + state.parent__c = state.references[state.references.length - 1].id; + return state; }); ``` See how instead of taking the id of the "last" thing that was created in Salesforce, you're taking the id of the 1st thing, or 2nd thing if you replace -"length-1" with "length-2". +`length - 1` with `length - 2`. ## Salesforce ### Convert date string to standard ISO date for Salesforce ```js -field('Payment_Date__c', function (state) { - return new Date(dataValue('payment_date')(state)).toISOString(); +fn(state => { + state.Payment_Date__c = new Date(state.data.payment_date).toISOString(); + return state; }); ``` @@ -105,7 +109,7 @@ field('Payment_Date__c', function (state) { array.map(item => { return { Patient_Name__c: item.fullName, - 'Account.Account_External_ID__c': item.account + 'Account.Account_External_ID__c': item.account, 'Clinic__r.Unique_Clinic_Identifier__c': item.clinicId, 'RecordType.Name': item.type, }; @@ -115,14 +119,7 @@ array.map(item => { ### Bulk upsert with an external ID in Salesforce ```js -bulk( - 'Visit_new__c', - 'upsert', - { - extIdField: 'commcare_case_id__c', - failOnError: true, - allowNoOp: true, - }, - dataValue('patients') -); +bulk2.upsert('Visit_new__c', 'commcare_case_id__c', $.data.patients, { + failOnError: true, +}); ``` diff --git a/docs/jobs/operations.md b/docs/jobs/operations.md index d20cc5643bb2..348bfb2b8294 100644 --- a/docs/jobs/operations.md +++ b/docs/jobs/operations.md @@ -61,10 +61,10 @@ Operations will only work when they are at the top level of your job code: ```js get('/patients'); each('$.data.patients[*]', state => { - item.id = `item-${index}`; + state.data.id = `item-${state.index}`; return state; }); -post('/patients', dataValue('patients')); +post('/patients', state => state.data.patients); ``` OpenFn calls your operations in series during workflow execution, ensuring the @@ -74,13 +74,14 @@ If you try to nest an operation inside the callback of another operation, it will fail: ```js -get('/patients', { headers: { 'content-type': 'application/json' } }, state => { +get('/patients', { headers: { 'content-type': 'application/json' } }).then(state => { // This will fail because it is nested in a callback each('$.data.patients[*]', (item, index) => { item.id = `item-${index}`; }); + return state; }); -post('/patients', dataValue('patients')); +post('/patients', state => state.data.patients); ``` This is because an operation is a "factory" function — when executed, it returns @@ -93,12 +94,13 @@ If you ever absolutely need a nested operation, you can immediately invoke it and pass state in directly — but this is an anti-pattern and should be avoided: ```js -get('/patients', { headers: { 'content-type': 'application/json' } }, state => { +get('/patients', { headers: { 'content-type': 'application/json' } }).then(state => { each('$.data.patients[*]', (item, index) => { item.id = `item-${index}`; })(state); // anti-pattern: immediately invoke and pass state + return state; }); -post('/patients', dataValue('patients')); +post('/patients', state => state.data.patients); ``` ## Reading state lazily @@ -133,9 +135,9 @@ correct OpenFn jobs. See also the :::caution -As of July 2024, callbacks are going to be phased out of the adaptor APIs. See -[Promise-like Operations](#promise-like-operations) for tips on how to use -callbacks with adaptors APIs that don't explicitly support them. +As of July 2024, callbacks have been deprecated from adaptor APIs. Use +[`.then()`](#callback-with-then) instead — it works on every operation and +provides the same functionality. ::: @@ -233,10 +235,10 @@ fn(state => { }); ``` -But you can also use a callback function, which is usually a bit neater: +But you can also use `.then()`, which is usually a bit neater: ```js -get('/data', {}, state => { +get('/data').then(state => { state.data = state.data.filter(/* ... */); return state; }); diff --git a/docs/jobs/state.md b/docs/jobs/state.md index e96962e4288f..ea92bbf76c44 100644 --- a/docs/jobs/state.md +++ b/docs/jobs/state.md @@ -147,7 +147,7 @@ The input state will look something like this: }, request: { method: "POST", - path: ['i', 'your-webhook-url-uuid'] // an ordered array with optional additional paths + path: ['i', 'your-webhook-url-uuid'], // an ordered array with optional additional paths headers: { "content-type": "application/json" }, // an object containing the headers of the request query_params: {} // an object containig any query parameters }, diff --git a/docs/jobs/using-cursors.md b/docs/jobs/using-cursors.md index 091c57249dd0..b61855e11043 100644 --- a/docs/jobs/using-cursors.md +++ b/docs/jobs/using-cursors.md @@ -84,11 +84,11 @@ and pass it into a HTTP query. Or perhaps you want to build the cursor into an object: ```js -get('registrations', state => { +get('registrations', state => ({ query: { - fromdate: state.cursor; - } -}); + fromdate: state.cursor, + }, +})); ``` The actual value of a cursor is arbitrary. You can use a string, a Date, a page @@ -98,7 +98,7 @@ You may want to advance the cursor at the end of a job ready, for the next run: ```js cursor(state => state.cursor, { defaultValue: 'today' }); -get(`/registrations?since={date.cursor}`); +get(`/registrations?since=${$.cursor}`); fn(/* do something good with your data */); cursor('now'); ``` From 0a6be0ff3250a3e305202ae509d6335e8238eb93 Mon Sep 17 00:00:00 2001 From: Hunter Achieng Date: Fri, 19 Jun 2026 13:23:04 +0300 Subject: [PATCH 2/2] fix: hoist clean helper in job-snippets concatenation example --- docs/jobs/job-snippets.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/jobs/job-snippets.md b/docs/jobs/job-snippets.md index 92673ef2b494..a4e6895192f0 100644 --- a/docs/jobs/job-snippets.md +++ b/docs/jobs/job-snippets.md @@ -52,10 +52,10 @@ This will concatenate many values, even if one or more are null, writing them to a field called Main_Office_City_c. ```js -fn(state => { - const clean = val => - val == null ? '' : val.toString().replace(/-/g, ' '); +const clean = val => + val == null ? '' : val.toString().replace(/-/g, ' '); +fn(state => { state.Main_Office_City__c = [ clean(state.data.Main_Office_City_a), clean(state.data.Main_Office_City_b),