diff --git a/.changeset/gentle-oranges-crash.md b/.changeset/gentle-oranges-crash.md
new file mode 100644
index 000000000..ba7d83753
--- /dev/null
+++ b/.changeset/gentle-oranges-crash.md
@@ -0,0 +1,21 @@
+---
+'@openfn/language-salesforce': major
+---
+
+New API design for salesforce, including adding composeNextState and removing
+old code.
+
+- Remove axios dependency
+- Remove old/unused functions. `relationship`, `upsertIf`, `createIf`,
+ `reference`, `steps`, `beta`, `describeAll()`
+- Standardize state mutation in all operation
+- Change `bulk` signature to `bulk(operation, sObjectName, records, options)`
+- Remove callback support
+
+### Migration Guide
+
+- Replace `describeAll()` with `describe()`.
+- Replace `upsertIf()` with `fnIf(true, upsert())`.
+- Replace `createIf()` with `fnIf(true, create())`.
+- Replace `bulk(operation, sObject, options, records)` with
+ `bulk(operation, sObjectName, records, options)`.
diff --git a/.changeset/lucky-chicken-notice.md b/.changeset/lucky-chicken-notice.md
new file mode 100644
index 000000000..0adc131bb
--- /dev/null
+++ b/.changeset/lucky-chicken-notice.md
@@ -0,0 +1,7 @@
+---
+'@openfn/language-salesforce': minor
+---
+
+- Create a `get()` and `post()` function for all http requests in salesforce
+ tions, records)`
+- Update `describe()` to fetch all available sObjects metadata
diff --git a/.changeset/wise-trees-carry.md b/.changeset/wise-trees-carry.md
new file mode 100644
index 000000000..8bac0ed1f
--- /dev/null
+++ b/.changeset/wise-trees-carry.md
@@ -0,0 +1,6 @@
+---
+'@openfn/language-salesforce': patch
+---
+
+- Change `cleanupState` to `removeConnection` and tagged it as private function
+- Rename `attrs` to `records`
diff --git a/packages/salesforce/ast.json b/packages/salesforce/ast.json
index 19f0db47c..d33f7fbe3 100644
--- a/packages/salesforce/ast.json
+++ b/packages/salesforce/ast.json
@@ -1,73 +1,15 @@
{
"operations": [
{
- "name": "relationship",
+ "name": "bulk",
"params": [
- "relationshipName",
- "externalId",
- "dataSource"
+ "sObjectName",
+ "operation",
+ "records",
+ "options"
],
"docs": {
- "description": "Adds a lookup relation or 'dome insert' to a record.",
- "tags": [
- {
- "title": "public",
- "description": null,
- "type": null
- },
- {
- "title": "example",
- "description": "Data Sourced Value:\n relationship(\"relationship_name__r\", \"externalID on related object\", dataSource(\"path\"))\nFixed Value:\n relationship(\"relationship_name__r\", \"externalID on related object\", \"hello world\")"
- },
- {
- "title": "function",
- "description": null,
- "name": null
- },
- {
- "title": "param",
- "description": "`__r` relationship field on the record.",
- "type": {
- "type": "NameExpression",
- "name": "string"
- },
- "name": "relationshipName"
- },
- {
- "title": "param",
- "description": "Salesforce ExternalID field.",
- "type": {
- "type": "NameExpression",
- "name": "string"
- },
- "name": "externalId"
- },
- {
- "title": "param",
- "description": "resolvable source.",
- "type": {
- "type": "NameExpression",
- "name": "string"
- },
- "name": "dataSource"
- },
- {
- "title": "returns",
- "description": null,
- "type": {
- "type": "NameExpression",
- "name": "object"
- }
- }
- ]
- },
- "valid": true
- },
- {
- "name": "describeAll",
- "params": [],
- "docs": {
- "description": "Prints the total number of all available sObjects and pushes the result to `state.references`.",
+ "description": "Create and execute a bulk job.",
"tags": [
{
"title": "public",
@@ -76,41 +18,13 @@
},
{
"title": "example",
- "description": "describeAll()"
- },
- {
- "title": "function",
- "description": null,
- "name": null
- },
- {
- "title": "returns",
- "description": null,
- "type": {
- "type": "NameExpression",
- "name": "Operation"
- }
- }
- ]
- },
- "valid": true
- },
- {
- "name": "describe",
- "params": [
- "sObject"
- ],
- "docs": {
- "description": "Prints an sObject metadata and pushes the result to state.references",
- "tags": [
- {
- "title": "public",
- "description": null,
- "type": null
+ "description": "bulk(\n \"Patient__c\",\n \"insert\",\n (state) => state.patients.map((x) => ({ Age__c: x.age, Name: x.name })),\n { failOnError: true }\n);",
+ "caption": "Bulk insert"
},
{
"title": "example",
- "description": "describe('obj_name')"
+ "description": "bulk(\n \"vera__Beneficiary__c\",\n \"upsert\",\n [\n {\n vera__Reporting_Period__c: 2023,\n vera__Geographic_Area__c: \"Uganda\",\n \"vera__Indicator__r.vera__ExtId__c\": 1001,\n vera__Result_UID__c: \"1001_2023_Uganda\",\n },\n ],\n { extIdField: \"vera__Result_UID__c\" }\n);",
+ "caption": "Bulk upsert"
},
{
"title": "function",
@@ -124,120 +38,25 @@
"type": "NameExpression",
"name": "string"
},
- "name": "sObject"
- },
- {
- "title": "returns",
- "description": null,
- "type": {
- "type": "NameExpression",
- "name": "Operation"
- }
- }
- ]
- },
- "valid": true
- },
- {
- "name": "retrieve",
- "params": [
- "sObject",
- "id",
- "callback"
- ],
- "docs": {
- "description": "Retrieves a Salesforce sObject(s).",
- "tags": [
- {
- "title": "public",
- "description": null,
- "type": null
- },
- {
- "title": "example",
- "description": "retrieve('ContentVersion', '0684K0000020Au7QAE/VersionData');"
- },
- {
- "title": "function",
- "description": null,
- "name": null
+ "name": "sObjectName"
},
{
"title": "param",
- "description": "The sObject to retrieve",
- "type": {
- "type": "NameExpression",
- "name": "string"
- },
- "name": "sObject"
- },
- {
- "title": "param",
- "description": "The id of the record",
+ "description": "The bulk operation to be performed.Eg \"insert\" | \"update\" | \"upsert\"",
"type": {
"type": "NameExpression",
"name": "string"
},
- "name": "id"
- },
- {
- "title": "param",
- "description": "A callback to execute once the record is retrieved",
- "type": {
- "type": "NameExpression",
- "name": "function"
- },
- "name": "callback"
- },
- {
- "title": "returns",
- "description": null,
- "type": {
- "type": "NameExpression",
- "name": "Operation"
- }
- }
- ]
- },
- "valid": true
- },
- {
- "name": "query",
- "params": [
- "qs",
- "options",
- "callback"
- ],
- "docs": {
- "description": "Execute an SOQL query.\nNote that in an event of a query error,\nerror logs will be printed but the operation will not throw the error.\n\nThe Salesforce query API is subject to rate limits, {@link https://sforce.co/3W9zyaQ See for more details}.",
- "tags": [
- {
- "title": "public",
- "description": null,
- "type": null
- },
- {
- "title": "example",
- "description": "query(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`);"
- },
- {
- "title": "example",
- "description": "query(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`, { autoFetch: true });",
- "caption": "Query more records if next records are available"
- },
- {
- "title": "function",
- "description": null,
- "name": null
+ "name": "operation"
},
{
"title": "param",
- "description": "A query string. Must be less than `4000` characters in WHERE clause",
+ "description": "an array of records, or a function which returns an array.",
"type": {
"type": "NameExpression",
- "name": "string"
+ "name": "array"
},
- "name": "qs"
+ "name": "records"
},
{
"title": "param",
@@ -250,97 +69,41 @@
},
{
"title": "param",
- "description": "Fetch next records if available.",
+ "description": "External id field.",
"type": {
"type": "OptionalType",
"expression": {
"type": "NameExpression",
- "name": "boolean"
+ "name": "string"
}
},
- "name": "options.autoFetch",
- "default": "false"
- },
- {
- "title": "param",
- "description": "A callback to execute once the record is retrieved",
- "type": {
- "type": "NameExpression",
- "name": "function"
- },
- "name": "callback"
- },
- {
- "title": "returns",
- "description": null,
- "type": {
- "type": "NameExpression",
- "name": "Operation"
- }
- }
- ]
- },
- "valid": false
- },
- {
- "name": "bulkQuery",
- "params": [
- "qs",
- "options",
- "callback"
- ],
- "docs": {
- "description": "Execute an SOQL Bulk Query.\nThis function uses bulk query to efficiently query large data sets and reduce the number of API requests.\n`bulkQuery()` uses {@link https://sforce.co/4azgczz Bulk API v.2.0 Query} which is available in API version 47.0 and later.\nThis API is subject to {@link https://sforce.co/4b6kn6z rate limits}.",
- "tags": [
- {
- "title": "public",
- "description": null,
- "type": null
- },
- {
- "title": "example",
- "description": "bulkQuery(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`);",
- "caption": "The results will be available on `state.data`"
- },
- {
- "title": "example",
- "description": "bulkQuery(\n (state) =>\n `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`,\n { pollTimeout: 10000, pollInterval: 6000 }\n);"
- },
- {
- "title": "function",
- "description": null,
- "name": null
- },
- {
- "title": "param",
- "description": "A query string.",
- "type": {
- "type": "NameExpression",
- "name": "string"
- },
- "name": "qs"
+ "name": "options.extIdField"
},
{
"title": "param",
- "description": "Options passed to the bulk api.",
+ "description": "Skipping bulk operation if no records.",
"type": {
- "type": "NameExpression",
- "name": "object"
+ "type": "OptionalType",
+ "expression": {
+ "type": "NameExpression",
+ "name": "boolean"
+ }
},
- "name": "options"
+ "name": "options.allowNoOp",
+ "default": "false"
},
{
"title": "param",
- "description": "Polling timeout in milliseconds.",
+ "description": "Fail the operation on error.",
"type": {
"type": "OptionalType",
"expression": {
"type": "NameExpression",
- "name": "integer"
+ "name": "boolean"
}
},
- "name": "options.pollTimeout",
- "default": "90000"
+ "name": "options.failOnError",
+ "default": "false"
},
{
"title": "param",
@@ -353,16 +116,20 @@
}
},
"name": "options.pollInterval",
- "default": "3000"
+ "default": "6000"
},
{
"title": "param",
- "description": "A callback to execute once the record is retrieved",
+ "description": "Polling timeout in milliseconds.",
"type": {
- "type": "NameExpression",
- "name": "function"
+ "type": "OptionalType",
+ "expression": {
+ "type": "NameExpression",
+ "name": "integer"
+ }
},
- "name": "callback"
+ "name": "options.pollTimeout",
+ "default": "240000"
},
{
"title": "returns",
@@ -377,15 +144,13 @@
"valid": false
},
{
- "name": "bulk",
+ "name": "bulkQuery",
"params": [
- "sObject",
- "operation",
- "options",
- "records"
+ "qs",
+ "options"
],
"docs": {
- "description": "Create and execute a bulk job.",
+ "description": "Execute an SOQL Bulk Query.\nThis function uses bulk query to efficiently query large data sets and reduce the number of API requests.\n`bulkQuery()` uses {@link https://sforce.co/4azgczz Bulk API v.2.0 Query} which is available in API version 47.0 and later.\nThis API is subject to {@link https://sforce.co/4b6kn6z rate limits}.",
"tags": [
{
"title": "public",
@@ -394,13 +159,12 @@
},
{
"title": "example",
- "description": "bulk(\n \"Patient__c\",\n \"insert\",\n { failOnError: true },\n (state) => state.someArray.map((x) => ({ Age__c: x.age, Name: x.name }))\n);",
- "caption": "Bulk insert"
+ "description": "bulkQuery(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`);",
+ "caption": "The results will be available on `state.data`"
},
{
"title": "example",
- "description": "bulk(\n \"vera__Beneficiary__c\",\n \"upsert\",\n { extIdField: \"vera__Result_UID__c\" },\n [\n {\n vera__Reporting_Period__c: 2023,\n vera__Geographic_Area__c: \"Uganda\",\n \"vera__Indicator__r.vera__ExtId__c\": 1001,\n vera__Result_UID__c: \"1001_2023_Uganda\",\n },\n ]\n);",
- "caption": "Bulk upsert"
+ "description": "bulkQuery(\n (state) =>\n `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`,\n { pollTimeout: 10000, pollInterval: 6000 }\n);"
},
{
"title": "function",
@@ -409,21 +173,12 @@
},
{
"title": "param",
- "description": "API name of the sObject.",
- "type": {
- "type": "NameExpression",
- "name": "string"
- },
- "name": "sObject"
- },
- {
- "title": "param",
- "description": "The bulk operation to be performed.Eg \"insert\" | \"update\" | \"upsert\"",
+ "description": "A query string.",
"type": {
"type": "NameExpression",
"name": "string"
},
- "name": "operation"
+ "name": "qs"
},
{
"title": "param",
@@ -445,54 +200,20 @@
}
},
"name": "options.pollTimeout",
- "default": "240000"
+ "default": "90000"
},
{
"title": "param",
"description": "Polling interval in milliseconds.",
"type": {
- "type": "OptionalType",
- "expression": {
- "type": "NameExpression",
- "name": "integer"
- }
- },
- "name": "options.pollInterval",
- "default": "6000"
- },
- {
- "title": "param",
- "description": "External id field.",
- "type": {
- "type": "OptionalType",
- "expression": {
- "type": "NameExpression",
- "name": "string"
- }
- },
- "name": "options.extIdField"
- },
- {
- "title": "param",
- "description": "Fail the operation on error.",
- "type": {
- "type": "OptionalType",
- "expression": {
- "type": "NameExpression",
- "name": "boolean"
- }
- },
- "name": "options.failOnError",
- "default": "false"
- },
- {
- "title": "param",
- "description": "an array of records, or a function which returns an array.",
- "type": {
- "type": "NameExpression",
- "name": "array"
+ "type": "OptionalType",
+ "expression": {
+ "type": "NameExpression",
+ "name": "integer"
+ }
},
- "name": "records"
+ "name": "options.pollInterval",
+ "default": "3000"
},
{
"title": "returns",
@@ -507,14 +228,13 @@
"valid": false
},
{
- "name": "destroy",
+ "name": "create",
"params": [
- "sObject",
- "attrs",
- "options"
+ "sObjectName",
+ "records"
],
"docs": {
- "description": "Delete records of an object.",
+ "description": "Create a new sObject record(s).",
"tags": [
{
"title": "public",
@@ -523,7 +243,13 @@
},
{
"title": "example",
- "description": "destroy('obj_name', [\n '0060n00000JQWHYAA5',\n '0090n00000JQEWHYAA5\n], { failOnError: true })"
+ "description": "create(\"Account\", { Name: \"My Account #1\" });",
+ "caption": "Single record creation"
+ },
+ {
+ "title": "example",
+ "description": "create(\"Account\",[{ Name: \"My Account #1\" }, { Name: \"My Account #2\" }]);",
+ "caption": "Multiple records creation"
},
{
"title": "function",
@@ -537,25 +263,16 @@
"type": "NameExpression",
"name": "string"
},
- "name": "sObject"
- },
- {
- "title": "param",
- "description": "Array of IDs of records to delete.",
- "type": {
- "type": "NameExpression",
- "name": "object"
- },
- "name": "attrs"
+ "name": "sObjectName"
},
{
"title": "param",
- "description": "Options for the destroy delete operation.",
+ "description": "Field attributes for the new record.",
"type": {
"type": "NameExpression",
"name": "object"
},
- "name": "options"
+ "name": "records"
},
{
"title": "returns",
@@ -570,13 +287,12 @@
"valid": true
},
{
- "name": "create",
+ "name": "describe",
"params": [
- "sObject",
- "attrs"
+ "sObjectName"
],
"docs": {
- "description": "Create a new sObject record(s).",
+ "description": "Fetches and prints metadata for an sObject and pushes the result to `state.data`.\nIf `sObjectName` is not specified, it will print the total number of all available sObjects and push the result to `state.data`.",
"tags": [
{
"title": "public",
@@ -585,13 +301,13 @@
},
{
"title": "example",
- "description": "create(\"Account\", { Name: \"My Account #1\" });",
- "caption": "Single record creation"
+ "description": "describe()",
+ "caption": "Fetch metadata for all available sObjects"
},
{
"title": "example",
- "description": "create(\"Account\",[{ Name: \"My Account #1\" }, { Name: \"My Account #2\" }]);",
- "caption": "Multiple records creation"
+ "description": "describe('Account')",
+ "caption": "Fetch metadata for Account sObject"
},
{
"title": "function",
@@ -600,21 +316,15 @@
},
{
"title": "param",
- "description": "API name of the sObject.",
- "type": {
- "type": "NameExpression",
- "name": "string"
- },
- "name": "sObject"
- },
- {
- "title": "param",
- "description": "Field attributes for the new record.",
+ "description": "The API name of the sObject. If omitted, fetches metadata for all sObjects.",
"type": {
- "type": "NameExpression",
- "name": "object"
+ "type": "OptionalType",
+ "expression": {
+ "type": "NameExpression",
+ "name": "string"
+ }
},
- "name": "attrs"
+ "name": "sObjectName"
},
{
"title": "returns",
@@ -629,13 +339,14 @@
"valid": true
},
{
- "name": "insert",
+ "name": "destroy",
"params": [
- "sObject",
- "attrs"
+ "sObjectName",
+ "ids",
+ "options"
],
"docs": {
- "description": "Alias for \"create(sObject, attrs)\".",
+ "description": "Delete records of an object.",
"tags": [
{
"title": "public",
@@ -644,13 +355,7 @@
},
{
"title": "example",
- "description": "insert(\"Account\", { Name: \"My Account #1\" });",
- "caption": "Single record creation"
- },
- {
- "title": "example",
- "description": "insert(\"Account\",[{ Name: \"My Account #1\" }, { Name: \"My Account #2\" }]);",
- "caption": "Multiple records creation"
+ "description": "destroy('obj_name', [\n '0060n00000JQWHYAA5',\n '0090n00000JQEWHYAA5'\n], { failOnError: true })"
},
{
"title": "function",
@@ -664,16 +369,25 @@
"type": "NameExpression",
"name": "string"
},
- "name": "sObject"
+ "name": "sObjectName"
},
{
"title": "param",
- "description": "Field attributes for the new record.",
+ "description": "Array of IDs of records to delete.",
+ "type": {
+ "type": "NameExpression",
+ "name": "object"
+ },
+ "name": "ids"
+ },
+ {
+ "title": "param",
+ "description": "Options for the destroy delete operation.",
"type": {
"type": "NameExpression",
"name": "object"
},
- "name": "attrs"
+ "name": "options"
},
{
"title": "returns",
@@ -688,14 +402,13 @@
"valid": true
},
{
- "name": "createIf",
+ "name": "insert",
"params": [
- "logical",
- "sObject",
- "attrs"
+ "sObjectName",
+ "records"
],
"docs": {
- "description": "Create a new sObject if conditions are met.\n\n**The `createIf()` function has been deprecated. Use `fnIf(condition,create())` instead.**",
+ "description": "Alias for \"create(sObjectName, attrs)\".",
"tags": [
{
"title": "public",
@@ -704,22 +417,19 @@
},
{
"title": "example",
- "description": "createIf(true, 'obj_name', {\n attr1: \"foo\",\n attr2: \"bar\"\n})"
+ "description": "insert(\"Account\", { Name: \"My Account #1\" });",
+ "caption": "Single record creation"
+ },
+ {
+ "title": "example",
+ "description": "insert(\"Account\",[{ Name: \"My Account #1\" }, { Name: \"My Account #2\" }]);",
+ "caption": "Multiple records creation"
},
{
"title": "function",
"description": null,
"name": null
},
- {
- "title": "param",
- "description": "a logical statement that will be evaluated.",
- "type": {
- "type": "NameExpression",
- "name": "boolean"
- },
- "name": "logical"
- },
{
"title": "param",
"description": "API name of the sObject.",
@@ -727,34 +437,16 @@
"type": "NameExpression",
"name": "string"
},
- "name": "sObject"
+ "name": "sObjectName"
},
{
"title": "param",
- "description": "Field attributes for the new object.",
+ "description": "Field attributes for the new record.",
"type": {
- "type": "UnionType",
- "elements": [
- {
- "type": "NameExpression",
- "name": "object"
- },
- {
- "type": "TypeApplication",
- "expression": {
- "type": "NameExpression",
- "name": "Array"
- },
- "applications": [
- {
- "type": "NameExpression",
- "name": "object"
- }
- ]
- }
- ]
+ "type": "NameExpression",
+ "name": "object"
},
- "name": "attrs"
+ "name": "records"
},
{
"title": "returns",
@@ -769,14 +461,13 @@
"valid": true
},
{
- "name": "upsert",
+ "name": "query",
"params": [
- "sObject",
- "externalId",
- "attrs"
+ "qs",
+ "options"
],
"docs": {
- "description": "Create a new sObject record, or updates it if it already exists\nExternal ID field name must be specified in second argument.",
+ "description": "Execute an SOQL query.\nNote that in an event of a query error,\nerror logs will be printed but the operation will not throw the error.\n\nThe Salesforce query API is subject to rate limits, {@link https://sforce.co/3W9zyaQ See for more details}.",
"tags": [
{
"title": "public",
@@ -785,13 +476,12 @@
},
{
"title": "example",
- "description": "upsert(\"UpsertTable__c\", \"ExtId__c\", { Name: \"Record #1\", ExtId__c : 'ID-0000001' });",
- "caption": "Single record upsert"
+ "description": "query(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`);"
},
{
"title": "example",
- "description": "upsert(\"UpsertTable__c\", \"ExtId__c\", [\n { Name: \"Record #1\", ExtId__c : 'ID-0000001' },\n { Name: \"Record #2\", ExtId__c : 'ID-0000002' },\n]);",
- "caption": "Multiple record upsert"
+ "description": "query(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`, { autoFetch: true });",
+ "caption": "Query more records if next records are available"
},
{
"title": "function",
@@ -800,60 +490,34 @@
},
{
"title": "param",
- "description": "API name of the sObject.",
+ "description": "A query string. Must be less than `4000` characters in WHERE clause",
"type": {
"type": "NameExpression",
"name": "string"
},
- "name": "sObject"
- },
- {
- "title": "magic",
- "description": "sObject - $.children[?(!@.meta.system)].name"
+ "name": "qs"
},
{
"title": "param",
- "description": "The external ID of the sObject.",
+ "description": "Options passed to the bulk api.",
"type": {
"type": "NameExpression",
- "name": "string"
+ "name": "object"
},
- "name": "externalId"
- },
- {
- "title": "magic",
- "description": "externalId - $.children[?(@.name==\"{{args.sObject}}\")].children[?(@.meta.externalId)].name"
+ "name": "options"
},
{
"title": "param",
- "description": "Field attributes for the new object.",
+ "description": "Fetch next records if available.",
"type": {
- "type": "UnionType",
- "elements": [
- {
- "type": "NameExpression",
- "name": "object"
- },
- {
- "type": "TypeApplication",
- "expression": {
- "type": "NameExpression",
- "name": "Array"
- },
- "applications": [
- {
- "type": "NameExpression",
- "name": "object"
- }
- ]
- }
- ]
+ "type": "OptionalType",
+ "expression": {
+ "type": "NameExpression",
+ "name": "boolean"
+ }
},
- "name": "attrs"
- },
- {
- "title": "magic",
- "description": "attrs - $.children[?(@.name==\"{{args.sObject}}\")].children[?(!@.meta.externalId)]"
+ "name": "options.autoFetch",
+ "default": "false"
},
{
"title": "returns",
@@ -865,18 +529,17 @@
}
]
},
- "valid": true
+ "valid": false
},
{
- "name": "upsertIf",
+ "name": "upsert",
"params": [
- "logical",
- "sObject",
+ "sObjectName",
"externalId",
- "attrs"
+ "records"
],
"docs": {
- "description": "Conditionally create a new sObject record, or updates it if it already exists\n\n**The `upsertIf()` function has been deprecated. Use `fnIf(condition,upsert())` instead.**",
+ "description": "Create a new sObject record, or updates it if it already exists\nExternal ID field name must be specified in second argument.",
"tags": [
{
"title": "public",
@@ -885,22 +548,19 @@
},
{
"title": "example",
- "description": "upsertIf(true, 'obj_name', 'ext_id', {\n attr1: \"foo\",\n attr2: \"bar\"\n})"
+ "description": "upsert(\"UpsertTable__c\", \"ExtId__c\", { Name: \"Record #1\", ExtId__c : 'ID-0000001' });",
+ "caption": "Single record upsert"
+ },
+ {
+ "title": "example",
+ "description": "upsert(\"UpsertTable__c\", \"ExtId__c\", [\n { Name: \"Record #1\", ExtId__c : 'ID-0000001' },\n { Name: \"Record #2\", ExtId__c : 'ID-0000002' },\n]);",
+ "caption": "Multiple record upsert"
},
{
"title": "function",
"description": null,
"name": null
},
- {
- "title": "param",
- "description": "a logical statement that will be evaluated.",
- "type": {
- "type": "NameExpression",
- "name": "boolean"
- },
- "name": "logical"
- },
{
"title": "param",
"description": "API name of the sObject.",
@@ -908,17 +568,25 @@
"type": "NameExpression",
"name": "string"
},
- "name": "sObject"
+ "name": "sObjectName"
+ },
+ {
+ "title": "magic",
+ "description": "sObjectName - $.children[?(!@.meta.system)].name"
},
{
"title": "param",
- "description": "ID.",
+ "description": "The external ID of the sObject.",
"type": {
"type": "NameExpression",
"name": "string"
},
"name": "externalId"
},
+ {
+ "title": "magic",
+ "description": "externalId - $.children[?(@.name==\"{{args.sObject}}\")].children[?(@.meta.externalId)].name"
+ },
{
"title": "param",
"description": "Field attributes for the new object.",
@@ -944,7 +612,11 @@
}
]
},
- "name": "attrs"
+ "name": "records"
+ },
+ {
+ "title": "magic",
+ "description": "records - $.children[?(@.name==\"{{args.sObject}}\")].children[?(!@.meta.externalId)]"
},
{
"title": "returns",
@@ -961,8 +633,8 @@
{
"name": "update",
"params": [
- "sObject",
- "attrs"
+ "sObjectName",
+ "records"
],
"docs": {
"description": "Update an sObject record or records.",
@@ -994,7 +666,7 @@
"type": "NameExpression",
"name": "string"
},
- "name": "sObject"
+ "name": "sObjectName"
},
{
"title": "param",
@@ -1021,7 +693,7 @@
}
]
},
- "name": "attrs"
+ "name": "records"
},
{
"title": "returns",
@@ -1036,12 +708,12 @@
"valid": true
},
{
- "name": "reference",
+ "name": "toUTF8",
"params": [
- "position"
+ "input"
],
"docs": {
- "description": "Get a reference ID by an index.",
+ "description": "Transliterates unicode characters to their best ASCII representation",
"tags": [
{
"title": "public",
@@ -1050,28 +722,23 @@
},
{
"title": "example",
- "description": "reference(0)"
- },
- {
- "title": "function",
- "description": null,
- "name": null
+ "description": "fn((state) => {\n const s = toUTF8(\"άνθρωποι\");\n console.log(s); // anthropoi\n return state;\n});"
},
{
"title": "param",
- "description": "Position for references array.",
+ "description": "A string with unicode characters",
"type": {
"type": "NameExpression",
- "name": "number"
+ "name": "string"
},
- "name": "position"
+ "name": "input"
},
{
"title": "returns",
- "description": null,
+ "description": "ASCII representation of input string",
"type": {
"type": "NameExpression",
- "name": "State"
+ "name": "string"
}
}
]
@@ -1079,12 +746,13 @@
"valid": true
},
{
- "name": "toUTF8",
+ "name": "retrieve",
"params": [
- "input"
+ "sObjectName",
+ "id"
],
"docs": {
- "description": "Transliterates unicode characters to their best ASCII representation",
+ "description": "Retrieves a Salesforce sObject(s).",
"tags": [
{
"title": "public",
@@ -1093,23 +761,37 @@
},
{
"title": "example",
- "description": "fn((state) => {\n const s = toUTF8(\"άνθρωποι\");\n console.log(s); // anthropoi\n return state;\n});"
+ "description": "retrieve('ContentVersion', '0684K0000020Au7QAE/VersionData');"
+ },
+ {
+ "title": "function",
+ "description": null,
+ "name": null
},
{
"title": "param",
- "description": "A string with unicode characters",
+ "description": "The sObject to retrieve",
"type": {
"type": "NameExpression",
"name": "string"
},
- "name": "input"
+ "name": "sObjectName"
},
{
- "title": "returns",
- "description": "ASCII representation of input string",
+ "title": "param",
+ "description": "The id of the record",
"type": {
"type": "NameExpression",
"name": "string"
+ },
+ "name": "id"
+ },
+ {
+ "title": "returns",
+ "description": null,
+ "type": {
+ "type": "NameExpression",
+ "name": "Operation"
}
}
]
diff --git a/packages/salesforce/package.json b/packages/salesforce/package.json
index dff6475c5..78ce5179d 100644
--- a/packages/salesforce/package.json
+++ b/packages/salesforce/package.json
@@ -33,7 +33,6 @@
"dependencies": {
"@openfn/language-common": "workspace:*",
"any-ascii": "^0.3.2",
- "axios": "^0.21.4",
"jsforce": "^1.11.1",
"lodash": "^4.17.21"
},
diff --git a/packages/salesforce/src/Adaptor.js b/packages/salesforce/src/Adaptor.js
index 77cf09a10..531bc6c7e 100644
--- a/packages/salesforce/src/Adaptor.js
+++ b/packages/salesforce/src/Adaptor.js
@@ -13,15 +13,13 @@
import {
execute as commonExecute,
- expandReferences,
- composeNextState,
- field,
chunk,
+ composeNextState,
} from '@openfn/language-common';
-import { expandReferences as newExpandReferences } from '@openfn/language-common/util';
+import { expandReferences } from '@openfn/language-common/util';
+import * as util from './Utils';
-import jsforce from 'jsforce';
import flatten from 'lodash/flatten';
let anyAscii = undefined;
@@ -35,328 +33,26 @@ const loadAnyAscii = state =>
});
/**
- * Adds a lookup relation or 'dome insert' to a record.
- * @public
- * @example
- * Data Sourced Value:
- * relationship("relationship_name__r", "externalID on related object", dataSource("path"))
- * Fixed Value:
- * relationship("relationship_name__r", "externalID on related object", "hello world")
- * @function
- * @param {string} relationshipName - `__r` relationship field on the record.
- * @param {string} externalId - Salesforce ExternalID field.
- * @param {string} dataSource - resolvable source.
- * @returns {object}
- */
-export function relationship(relationshipName, externalId, dataSource) {
- return field(relationshipName, state => {
- if (typeof dataSource == 'function') {
- return { [externalId]: dataSource(state) };
- }
- return { [externalId]: dataSource };
- });
-}
-
-/**
- * Prints the total number of all available sObjects and pushes the result to `state.references`.
- * @public
- * @example
- * describeAll()
- * @function
- * @returns {Operation}
- */
-export function describeAll() {
- return state => {
- const { connection } = state;
-
- return connection.describeGlobal().then(result => {
- const { sobjects } = result;
- console.log(`Retrieved ${sobjects.length} sObjects`);
-
- return {
- ...state,
- references: [sobjects, ...state.references],
- };
- });
- };
-}
-
-/**
- * Prints an sObject metadata and pushes the result to state.references
- * @public
- * @example
- * describe('obj_name')
+ * Executes an operation.
* @function
- * @param {string} sObject - API name of the sObject.
- * @returns {Operation}
+ * @private
+ * @param {Operation} operations - Operations
+ * @returns {State}
*/
-export function describe(sObject) {
- return state => {
- const { connection } = state;
-
- const objectName = expandReferences(sObject)(state);
-
- return connection
- .sobject(objectName)
- .describe()
- .then(result => {
- console.log('Label : ' + result.label);
- console.log('Num of Fields : ' + result.fields.length);
-
- return {
- ...state,
- references: [result, ...state.references],
- };
- });
+export function execute(...operations) {
+ const initialState = {
+ references: [],
+ data: null,
+ configuration: {},
};
-}
-/**
- * Retrieves a Salesforce sObject(s).
- * @public
- * @example
- * retrieve('ContentVersion', '0684K0000020Au7QAE/VersionData');
- * @function
- * @param {string} sObject - The sObject to retrieve
- * @param {string} id - The id of the record
- * @param {function} callback - A callback to execute once the record is retrieved
- * @returns {Operation}
- */
-export function retrieve(sObject, id, callback) {
return state => {
- const { connection } = state;
-
- const finalId = expandReferences(id)(state);
-
- return connection
- .sobject(sObject)
- .retrieve(finalId)
- .then(result => {
- return {
- ...state,
- references: [result, ...state.references],
- };
- })
- .then(state => {
- if (callback) {
- return callback(state);
- }
- return state;
- });
- };
-}
-
-/**
- * Execute an SOQL query.
- * Note that in an event of a query error,
- * error logs will be printed but the operation will not throw the error.
- *
- * The Salesforce query API is subject to rate limits, {@link https://sforce.co/3W9zyaQ See for more details}.
- * @public
- * @example
- * query(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`);
- * @example
Query more records if next records are available
- * query(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`, { autoFetch: true });
- * @function
- * @param {string} qs - A query string. Must be less than `4000` characters in WHERE clause
- * @param {object} options - Options passed to the bulk api.
- * @param {boolean} [options.autoFetch=false] - Fetch next records if available.
- * @param {function} callback - A callback to execute once the record is retrieved
- * @returns {Operation}
- */
-export function query(qs, options, callback = s => s) {
- return async state => {
- let done = false;
- let qResult = null;
- let result = [];
-
- const { connection } = state;
- const [resolvedQs, resolvedOptions] = newExpandReferences(
- state,
- qs,
- options
- );
- const { autoFetch } = { ...{ autoFetch: false }, ...resolvedOptions };
-
- console.log(`Executing query: ${resolvedQs}`);
- try {
- qResult = await connection.query(resolvedQs);
- } catch (err) {
- const { message, errorCode } = err;
- console.log(`Error ${errorCode}: ${message}`);
- throw err;
- }
-
- if (qResult.totalSize > 0) {
- console.log('Total records', qResult.totalSize);
-
- while (!done) {
- result.push(qResult);
-
- if (qResult.done) {
- done = true;
- } else if (autoFetch) {
- console.log(
- 'Fetched records so far',
- result.map(ref => ref.records).flat().length
- );
- console.log('Fetching next records...');
- try {
- qResult = await connection.request({ url: qResult.nextRecordsUrl });
- } catch (err) {
- const { message, errorCode } = err;
- console.log(`Error ${errorCode}: ${message}`);
- throw err;
- }
- } else {
- done = true;
- }
- }
-
- console.log(
- 'Done ✔ retrieved records',
- result.map(ref => ref.records).flat().length
- );
- } else {
- result.push(qResult);
- console.log('No records found.');
- }
-
- console.log(
- 'Results retrieved and pushed to position [0] of the references array.'
- );
-
- const nextState = {
- ...state,
- references: [result, ...state.references],
- };
- return callback(nextState);
- };
-}
-
-async function pollJobResult(conn, job, pollInterval, pollTimeout) {
- let attempt = 0;
-
- const maxPollingAttempts = Math.floor(pollTimeout / pollInterval);
-
- while (attempt < maxPollingAttempts) {
- // Make an HTTP GET request to check the job status
- const jobInfo = await conn
- .request({
- method: 'GET',
- url: `/services/data/v${conn.version}/jobs/query/${job.id}`,
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .catch(error => {
- console.log('Failed to fetch job information', error);
- });
-
- if (jobInfo && jobInfo.state === 'JobComplete') {
- const response = await conn.request({
- method: 'GET',
- url: `/services/data/v${conn.version}/jobs/query/${job.id}/results`,
- headers: {
- 'Content-Type': 'application/json',
- },
- });
-
- console.log('Job result retrieved', response.length);
- return response;
- } else {
- // Handle maxPollingAttempts
- if (attempt + 1 === maxPollingAttempts) {
- console.error(
- 'Maximum polling attempt reached, Please increase pollInterval and pollTimeout'
- );
- throw new Error(`Polling time out. Job Id = ${job.id}`);
- }
- console.log(
- `Attempt ${attempt + 1} - Job ${jobInfo.id} is still in ${
- jobInfo.state
- }:`
- );
- }
-
- // Wait for the polling interval before the next attempt
- await new Promise(resolve => setTimeout(resolve, pollInterval));
- attempt++;
- }
-}
-
-const defaultOptions = {
- pollTimeout: 90000, // in ms
- pollInterval: 3000, // in ms
-};
-/**
- * Execute an SOQL Bulk Query.
- * This function uses bulk query to efficiently query large data sets and reduce the number of API requests.
- * `bulkQuery()` uses {@link https://sforce.co/4azgczz Bulk API v.2.0 Query} which is available in API version 47.0 and later.
- * This API is subject to {@link https://sforce.co/4b6kn6z rate limits}.
- * @public
- * @example
- * The results will be available on `state.data`
- * bulkQuery(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`);
- * @example
- * bulkQuery(
- * (state) =>
- * `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`,
- * { pollTimeout: 10000, pollInterval: 6000 }
- * );
- * @function
- * @param {string} qs - A query string.
- * @param {object} options - Options passed to the bulk api.
- * @param {integer} [options.pollTimeout=90000] - Polling timeout in milliseconds.
- * @param {integer} [options.pollInterval=3000] - Polling interval in milliseconds.
- * @param {function} callback - A callback to execute once the record is retrieved
- * @returns {Operation}
- */
-export function bulkQuery(qs, options, callback) {
- return async state => {
- const { connection } = state;
- const [resolvedQs, resolvedOptions] = newExpandReferences(
- state,
- qs,
- options
- );
-
- if (parseFloat(connection.version) < 47.0)
- throw new Error('bulkQuery requires API version 47.0 and later');
-
- const { pollTimeout, pollInterval } = {
- ...defaultOptions,
- ...resolvedOptions,
- };
-
- console.log(`Executing query: ${resolvedQs}`);
-
- const queryJob = await connection.request({
- method: 'POST',
- url: `/services/data/v${connection.version}/jobs/query`,
- body: JSON.stringify({
- operation: 'query',
- query: resolvedQs,
- }),
- headers: {
- 'Content-Type': 'application/json',
- },
- });
-
- const result = await pollJobResult(
- connection,
- queryJob,
- pollInterval,
- pollTimeout
- );
-
- const nextState = {
- ...composeNextState(state, result),
- result,
- };
- if (callback) return callback(nextState);
-
- return nextState;
+ return commonExecute(
+ loadAnyAscii,
+ util.createConnection,
+ ...flatten(operations),
+ util.removeConnection
+ )({ ...initialState, ...state });
};
}
@@ -367,14 +63,13 @@ export function bulkQuery(qs, options, callback) {
* bulk(
* "Patient__c",
* "insert",
- * { failOnError: true },
- * (state) => state.someArray.map((x) => ({ Age__c: x.age, Name: x.name }))
+ * (state) => state.patients.map((x) => ({ Age__c: x.age, Name: x.name })),
+ * { failOnError: true }
* );
* @example Bulk upsert
* bulk(
* "vera__Beneficiary__c",
* "upsert",
- * { extIdField: "vera__Result_UID__c" },
* [
* {
* vera__Reporting_Period__c: 2023,
@@ -382,40 +77,42 @@ export function bulkQuery(qs, options, callback) {
* "vera__Indicator__r.vera__ExtId__c": 1001,
* vera__Result_UID__c: "1001_2023_Uganda",
* },
- * ]
+ * ],
+ * { extIdField: "vera__Result_UID__c" }
* );
* @function
- * @param {string} sObject - API name of the sObject.
+ * @param {string} sObjectName - API name of the sObject.
* @param {string} operation - The bulk operation to be performed.Eg "insert" | "update" | "upsert"
+ * @param {array} records - an array of records, or a function which returns an array.
* @param {object} options - Options passed to the bulk api.
- * @param {integer} [options.pollTimeout=240000] - Polling timeout in milliseconds.
- * @param {integer} [options.pollInterval=6000] - Polling interval in milliseconds.
* @param {string} [options.extIdField] - External id field.
+ * @param {boolean} [options.allowNoOp=false] - Skipping bulk operation if no records.
* @param {boolean} [options.failOnError=false] - Fail the operation on error.
- * @param {array} records - an array of records, or a function which returns an array.
+ * @param {integer} [options.pollInterval=6000] - Polling interval in milliseconds.
+ * @param {integer} [options.pollTimeout=240000] - Polling timeout in milliseconds.
* @returns {Operation}
*/
-export function bulk(sObject, operation, options, records) {
+export function bulk(sObjectName, operation, records, options = {}) {
return state => {
const { connection } = state;
const [
- resolvedSObject,
+ resolvedSObjectName,
resolvedOperation,
- resolvedOptions,
resolvedRecords,
- ] = newExpandReferences(state, sObject, operation, options, records);
+ resolvedOptions,
+ ] = expandReferences(state, sObjectName, operation, records, options);
const {
failOnError = false,
allowNoOp = false,
- pollTimeout,
- pollInterval,
+ pollTimeout = 240000,
+ pollInterval = 6000,
} = resolvedOptions;
if (allowNoOp && resolvedRecords.length === 0) {
console.info(
- `No items in ${resolvedSObject} array. Skipping bulk ${resolvedOperation} operation.`
+ `No items in ${resolvedSObjectName} array. Skipping bulk ${resolvedOperation} operation.`
);
return state;
}
@@ -429,17 +126,14 @@ export function bulk(sObject, operation, options, records) {
chunkedBatches.map(
chunkedBatch =>
new Promise((resolve, reject) => {
- const timeout = pollTimeout || 240000;
- const interval = pollInterval || 6000;
-
console.info(
- `Creating bulk ${resolvedOperation} job for ${resolvedSObject} with ${chunkedBatch.length} records`
+ `Creating bulk ${resolvedOperation} job for ${resolvedSObjectName} with ${chunkedBatch.length} records`
);
const job = connection.bulk.createJob(
- resolvedSObject,
+ resolvedSObjectName,
resolvedOperation,
- options
+ resolvedOptions
);
job.on('error', err => reject(err));
@@ -461,7 +155,7 @@ export function bulk(sObject, operation, options, records) {
console.info(batchInfo);
const batchId = batchInfo.id;
var batch = job.batch(batchId);
- batch.poll(interval, timeout);
+ batch.poll(pollInterval, pollTimeout);
})
.then(async res => {
await job.close();
@@ -471,26 +165,148 @@ export function bulk(sObject, operation, options, records) {
return !item.success;
});
- errors.forEach(err => {
- err[`${options.extIdField}`] =
- chunkedBatch[err.position - 1][options.extIdField];
- });
+ errors.forEach(err => {
+ err[`${resolvedOptions.extIdField}`] =
+ chunkedBatch[err.position - 1][resolvedOptions.extIdField];
+ });
+
+ if (failOnError && errors.length > 0) {
+ console.error('Errors detected:');
+ reject(JSON.stringify(errors, null, 2));
+ } else {
+ console.log('Result : ' + JSON.stringify(res, null, 2));
+ resolve(res);
+ }
+ });
+ })
+ )
+ ).then(results => {
+ console.log('Merging results arrays.');
+ return composeNextState(state, results.flat());
+ });
+ };
+}
+/**
+ * Execute an SOQL Bulk Query.
+ * This function uses bulk query to efficiently query large data sets and reduce the number of API requests.
+ * `bulkQuery()` uses {@link https://sforce.co/4azgczz Bulk API v.2.0 Query} which is available in API version 47.0 and later.
+ * This API is subject to {@link https://sforce.co/4b6kn6z rate limits}.
+ * @public
+ * @example
+ * The results will be available on `state.data`
+ * bulkQuery(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`);
+ * @example
+ * bulkQuery(
+ * (state) =>
+ * `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`,
+ * { pollTimeout: 10000, pollInterval: 6000 }
+ * );
+ * @function
+ * @param {string} qs - A query string.
+ * @param {object} options - Options passed to the bulk api.
+ * @param {integer} [options.pollTimeout=90000] - Polling timeout in milliseconds.
+ * @param {integer} [options.pollInterval=3000] - Polling interval in milliseconds.
+ * @returns {Operation}
+ */
+export function bulkQuery(qs, options = {}) {
+ return async state => {
+ const { connection } = state;
+ const [resolvedQs, resolvedOptions] = expandReferences(state, qs, options);
+
+ if (parseFloat(connection.version) < 47.0)
+ throw new Error('bulkQuery requires API version 47.0 and later');
+
+ const { pollTimeout = 90000, pollInterval = 3000 } = resolvedOptions;
+
+ console.log(`Executing query: ${resolvedQs}`);
+
+ const queryJob = await connection.request({
+ method: 'POST',
+ url: `/services/data/v${connection.version}/jobs/query`,
+ body: JSON.stringify({
+ operation: 'query',
+ query: resolvedQs,
+ }),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ const result = await util.pollJobResult(
+ connection,
+ queryJob,
+ pollInterval,
+ pollTimeout
+ );
+
+ return composeNextState(state, result);
+ };
+}
+
+/**
+ * Create a new sObject record(s).
+ * @public
+ * @example Single record creation
+ * create("Account", { Name: "My Account #1" });
+ * @example Multiple records creation
+ * create("Account",[{ Name: "My Account #1" }, { Name: "My Account #2" }]);
+ * @function
+ * @param {string} sObjectName - API name of the sObject.
+ * @param {object} records - Field attributes for the new record.
+ * @returns {Operation}
+ */
+export function create(sObjectName, records) {
+ return state => {
+ let { connection } = state;
+ const [resolvedSObjectName, resolvedRecords] = expandReferences(
+ state,
+ sObjectName,
+ records
+ );
+ console.info(`Creating ${resolvedSObjectName}`, resolvedRecords);
+
+ return connection
+ .create(resolvedSObjectName, resolvedRecords)
+ .then(recordResult => {
+ console.log('Result : ' + JSON.stringify(recordResult));
+ return composeNextState(state, recordResult);
+ });
+ };
+}
+
+/**
+ * Fetches and prints metadata for an sObject and pushes the result to `state.data`.
+ * If `sObjectName` is not specified, it will print the total number of all available sObjects and push the result to `state.data`.
+ * @public
+ * @example Fetch metadata for all available sObjects
+ * describe()
+ * @example Fetch metadata for Account sObject
+ * describe('Account')
+ * @function
+ * @param {string} [sObjectName] - The API name of the sObject. If omitted, fetches metadata for all sObjects.
+ * @returns {Operation}
+ */
+export function describe(sObjectName) {
+ return state => {
+ const { connection } = state;
+
+ const [resolvedSObjectName] = expandReferences(state, sObjectName);
- if (failOnError && errors.length > 0) {
- console.error('Errors detected:');
- reject(JSON.stringify(errors, null, 2));
- } else {
- console.log('Result : ' + JSON.stringify(res, null, 2));
- resolve(res);
- }
- });
+ return resolvedSObjectName
+ ? connection
+ .sobject(resolvedSObjectName)
+ .describe()
+ .then(result => {
+ console.log('Label : ' + result.label);
+ console.log('Num of Fields : ' + result.fields.length);
+
+ return composeNextState(state, result);
})
- )
- ).then(arrayOfResults => {
- console.log('Merging results arrays.');
- const merged = [].concat.apply([], arrayOfResults);
- return { ...state, references: [merged, ...state.references] };
- });
+ : connection.describeGlobal().then(result => {
+ const { sobjects } = result;
+ console.log(`Retrieved ${sobjects.length} sObjects`);
+ return composeNextState(state, result);
+ });
};
}
@@ -500,132 +316,206 @@ export function bulk(sObject, operation, options, records) {
* @example
* destroy('obj_name', [
* '0060n00000JQWHYAA5',
- * '0090n00000JQEWHYAA5
+ * '0090n00000JQEWHYAA5'
* ], { failOnError: true })
* @function
- * @param {string} sObject - API name of the sObject.
- * @param {object} attrs - Array of IDs of records to delete.
+ * @param {string} sObjectName - API name of the sObject.
+ * @param {object} ids - Array of IDs of records to delete.
* @param {object} options - Options for the destroy delete operation.
* @returns {Operation}
*/
-export function destroy(sObject, attrs, options) {
+export function destroy(sObjectName, ids, options = {}) {
return state => {
const { connection } = state;
- const finalAttrs = expandReferences(attrs)(state);
- const { failOnError } = options;
- console.info(`Deleting ${sObject} records`);
+ const [resolvedSObjectName, resolvedIds, resolvedOptions] =
+ expandReferences(state, sObjectName, ids, options);
+
+ const { failOnError = false } = resolvedOptions;
+
+ console.info(`Deleting ${resolvedSObjectName} records`);
return connection
- .sobject(sObject)
- .del(finalAttrs)
+ .sobject(resolvedSObjectName)
+ .del(resolvedIds)
.then(function (result) {
const successes = result.filter(r => r.success);
+ const failures = result.filter(r => !r.success);
+
console.log(
'Sucessfully deleted: ',
JSON.stringify(successes, null, 2)
);
- const failures = result.filter(r => !r.success);
- console.log('Failed to delete: ', JSON.stringify(failures, null, 2));
+ if (failures.length > 0) {
+ console.log('Failed to delete: ', JSON.stringify(failures, null, 2));
- if (failOnError && result.some(r => !r.success))
- throw 'Some deletes failed; exiting with failure code.';
+ if (failOnError)
+ throw 'Some deletes failed; exiting with failure code.';
+ }
- return {
- ...state,
- references: [result, ...state.references],
- };
+ return composeNextState(state, result);
});
};
}
/**
- * Create a new sObject record(s).
- * @public
- * @example Single record creation
- * create("Account", { Name: "My Account #1" });
- * @example Multiple records creation
- * create("Account",[{ Name: "My Account #1" }, { Name: "My Account #2" }]);
- * @function
- * @param {string} sObject - API name of the sObject.
- * @param {object} attrs - Field attributes for the new record.
+ * Send a GET HTTP request using connected session information.
+ * @example
+ * get('/actions/custom/flow/POC_OpenFN_Test_Flow');
+ * @param {string} path - The Salesforce API endpoint, Relative to request from
+ * @param {object} options - Request query parameters and headers
* @returns {Operation}
*/
-export function create(sObject, attrs) {
- return state => {
- let { connection } = state;
- const finalAttrs = expandReferences(attrs)(state);
- console.info(`Creating ${sObject}`, finalAttrs);
-
- return connection.create(sObject, finalAttrs).then(function (recordResult) {
- console.log('Result : ' + JSON.stringify(recordResult));
- return {
- ...state,
- references: [recordResult, ...state.references],
- };
- });
+export function get(path, options = {}) {
+ return async state => {
+ const { connection } = state;
+ const [resolvedPath, resolvedOptions] = expandReferences(
+ state,
+ path,
+ options
+ );
+ const { headers, ...query } = resolvedOptions;
+ console.log(`GET: ${resolvedPath}`);
+ const requestOptions = {
+ url: resolvedPath,
+ method: 'GET',
+ query,
+ headers: { 'content-type': 'application/json', ...headers },
+ };
+
+ const result = await connection.request(requestOptions);
+
+ return composeNextState(state, result);
};
}
/**
- * Alias for "create(sObject, attrs)".
+ * Alias for "create(sObjectName, attrs)".
* @public
* @example Single record creation
* insert("Account", { Name: "My Account #1" });
* @example Multiple records creation
* insert("Account",[{ Name: "My Account #1" }, { Name: "My Account #2" }]);
* @function
- * @param {string} sObject - API name of the sObject.
- * @param {object} attrs - Field attributes for the new record.
+ * @param {string} sObjectName - API name of the sObject.
+ * @param {object} records - Field attributes for the new record.
+ * @returns {Operation}
+ */
+export function insert(sObjectName, records) {
+ return create(sObjectName, records);
+}
+
+/**
+ * Send a POST HTTP request using connected session information.
+ *
+ * @example
+ * post('/actions/custom/flow/POC_OpenFN_Test_Flow', { inputs: [{}] });
+ * @param {string} path - The Salesforce API endpoint, Relative to request from
+ * @param {object} data - A JSON Object request body
+ * @param {object} options - Request options
+ * @param {object} [options.headers] - Object of request headers
+ * @param {object} [options.query] - A JSON Object request body
* @returns {Operation}
*/
-export function insert(sObject, attrs) {
- return create(sObject, attrs);
+export function post(path, data, options = {}) {
+ return async state => {
+ const { connection } = state;
+ const [resolvedPath, resolvedData, resolvedOptions] = expandReferences(
+ state,
+ path,
+ data,
+ options
+ );
+ const { query, headers } = resolvedOptions;
+
+ console.log(`POST: ${resolvedPath}`);
+
+ const requestOptions = {
+ url: resolvedPath,
+ method: 'POST',
+ query,
+ headers: { 'content-type': 'application/json', ...headers },
+ body: JSON.stringify(resolvedData),
+ };
+
+ const result = await connection.request(requestOptions);
+
+ return composeNextState(state, result);
+ };
}
/**
- * Create a new sObject if conditions are met.
+ * Execute an SOQL query.
+ * Note that in an event of a query error,
+ * error logs will be printed but the operation will not throw the error.
*
- * **The `createIf()` function has been deprecated. Use `fnIf(condition,create())` instead.**
+ * The Salesforce query API is subject to rate limits, {@link https://sforce.co/3W9zyaQ See for more details}.
* @public
* @example
- * createIf(true, 'obj_name', {
- * attr1: "foo",
- * attr2: "bar"
- * })
+ * query(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`);
+ * @example Query more records if next records are available
+ * query(state=> `SELECT Id FROM Patient__c WHERE Health_ID__c = '${state.data.field1}'`, { autoFetch: true });
* @function
- * @param {boolean} logical - a logical statement that will be evaluated.
- * @param {string} sObject - API name of the sObject.
- * @param {(object|object[])} attrs - Field attributes for the new object.
+ * @param {string} qs - A query string. Must be less than `4000` characters in WHERE clause
+ * @param {object} options - Options passed to the bulk api.
+ * @param {boolean} [options.autoFetch=false] - Fetch next records if available.
* @returns {Operation}
*/
-export function createIf(logical, sObject, attrs) {
- return state => {
- const resolvedLogical = expandReferences(logical)(state);
+export function query(qs, options = {}) {
+ return async state => {
+ let done = false;
+ let qResult = null;
+ let result = [];
- console.warn(
- `The 'createIf()' function has been deprecated. Use 'fnIf(condition,create())' instead.`
- );
+ const { connection } = state;
+ const [resolvedQs, resolvedOptions] = expandReferences(state, qs, options);
+ const { autoFetch = false } = resolvedOptions;
- if (resolvedLogical) {
- const { connection } = state;
- const finalAttrs = expandReferences(attrs)(state);
- console.info(`Creating ${sObject}`, finalAttrs);
- return connection
- .create(sObject, finalAttrs)
- .then(function (recordResult) {
- console.log('Result : ' + JSON.stringify(recordResult));
- return {
- ...state,
- references: [recordResult, ...state.references],
- };
- });
+ console.log(`Executing query: ${resolvedQs}`);
+ try {
+ qResult = await connection.query(resolvedQs);
+ } catch (err) {
+ const { message, errorCode } = err;
+ console.log(`Error ${errorCode}: ${message}`);
+ throw err;
+ }
+
+ if (qResult.totalSize > 0) {
+ console.log('Total records', qResult.totalSize);
+
+ while (!done) {
+ result.push(qResult);
+
+ if (qResult.done) {
+ done = true;
+ } else if (autoFetch) {
+ console.log(
+ 'Fetched records so far',
+ result.map(ref => ref.records).flat().length
+ );
+ console.log('Fetching next records...');
+ try {
+ qResult = await connection.request({ url: qResult.nextRecordsUrl });
+ } catch (err) {
+ const { message, errorCode } = err;
+ console.log(`Error ${errorCode}: ${message}`);
+ throw err;
+ }
+ } else {
+ done = true;
+ }
+ }
+
+ console.log(
+ 'Done ✔ retrieved records',
+ result.map(ref => ref.records).flat().length
+ );
} else {
- console.info(`Not creating ${sObject} because logical is false.`);
- return {
- ...state,
- };
+ result.push(qResult);
+ console.log('No records found.');
}
+
+ return composeNextState(state, result, result?.records);
};
}
@@ -641,87 +531,33 @@ export function createIf(logical, sObject, attrs) {
* { Name: "Record #2", ExtId__c : 'ID-0000002' },
* ]);
* @function
- * @param {string} sObject - API name of the sObject.
- * @magic sObject - $.children[?(!@.meta.system)].name
+ * @param {string} sObjectName - API name of the sObject.
+ * @magic sObjectName - $.children[?(!@.meta.system)].name
* @param {string} externalId - The external ID of the sObject.
* @magic externalId - $.children[?(@.name=="{{args.sObject}}")].children[?(@.meta.externalId)].name
- * @param {(object|object[])} attrs - Field attributes for the new object.
- * @magic attrs - $.children[?(@.name=="{{args.sObject}}")].children[?(!@.meta.externalId)]
+ * @param {(object|object[])} records - Field attributes for the new object.
+ * @magic records - $.children[?(@.name=="{{args.sObject}}")].children[?(!@.meta.externalId)]
* @returns {Operation}
*/
-export function upsert(sObject, externalId, attrs) {
+export function upsert(sObjectName, externalId, records) {
return state => {
const { connection } = state;
- const finalAttrs = expandReferences(attrs)(state);
+ const [resolvedSObjectName, resolvedExternalId, resolvedRecords] =
+ expandReferences(state, sObjectName, externalId, records);
console.info(
- `Upserting ${sObject} with externalId`,
- externalId,
+ `Upserting ${resolvedSObjectName} with externalId`,
+ resolvedExternalId,
':',
- finalAttrs
+ resolvedRecords
);
return connection
- .upsert(sObject, finalAttrs, externalId)
- .then(function (recordResult) {
- console.log('Result : ' + JSON.stringify(recordResult));
- return {
- ...state,
- references: [recordResult, ...state.references],
- };
- });
- };
-}
-
-/**
- * Conditionally create a new sObject record, or updates it if it already exists
- *
- * **The `upsertIf()` function has been deprecated. Use `fnIf(condition,upsert())` instead.**
- * @public
- * @example
- * upsertIf(true, 'obj_name', 'ext_id', {
- * attr1: "foo",
- * attr2: "bar"
- * })
- * @function
- * @param {boolean} logical - a logical statement that will be evaluated.
- * @param {string} sObject - API name of the sObject.
- * @param {string} externalId - ID.
- * @param {(object|object[])} attrs - Field attributes for the new object.
- * @returns {Operation}
- */
-export function upsertIf(logical, sObject, externalId, attrs) {
- return state => {
- const resolvedLogical = expandReferences(logical)(state);
-
- console.warn(
- `The 'upsertIf()' function has been deprecated. Use 'fnIf(condition,upsert())' instead.`
- );
-
- if (resolvedLogical) {
- const { connection } = state;
- const finalAttrs = expandReferences(attrs)(state);
- console.info(
- `Upserting ${sObject} with externalId`,
- externalId,
- ':',
- finalAttrs
- );
+ .upsert(resolvedSObjectName, resolvedRecords, resolvedExternalId)
+ .then(function (result) {
+ console.log('Result : ' + JSON.stringify(result));
- return connection
- .upsert(sObject, finalAttrs, externalId)
- .then(function (recordResult) {
- console.log('Result : ' + JSON.stringify(recordResult));
- return {
- ...state,
- references: [recordResult, ...state.references],
- };
- });
- } else {
- console.info(`Not upserting ${sObject} because logical is false.`);
- return {
- ...state,
- };
- }
+ return composeNextState(state, result);
+ });
};
}
@@ -739,163 +575,28 @@ export function upsertIf(logical, sObject, externalId, attrs) {
* { Id: "0010500000fxbcvAAA", Name: "Updated Account #2" },
* ]);
* @function
- * @param {string} sObject - API name of the sObject.
- * @param {(object|object[])} attrs - Field attributes for the new object.
+ * @param {string} sObjectName - API name of the sObject.
+ * @param {(object|object[])} records - Field attributes for the new object.
* @returns {Operation}
*/
-export function update(sObject, attrs) {
+export function update(sObjectName, records) {
return state => {
let { connection } = state;
- const finalAttrs = expandReferences(attrs)(state);
- console.info(`Updating ${sObject}`, finalAttrs);
-
- return connection.update(sObject, finalAttrs).then(function (recordResult) {
- console.log('Result : ' + JSON.stringify(recordResult));
- return {
- ...state,
- references: [recordResult, ...state.references],
- };
- });
- };
-}
-
-/**
- * Get a reference ID by an index.
- * @public
- * @example
- * reference(0)
- * @function
- * @param {number} position - Position for references array.
- * @returns {State}
- */
-export function reference(position) {
- return state => state.references[position].id;
-}
-
-function getConnection(state, options) {
- const { apiVersion } = state.configuration;
-
- const apiVersionRegex = /^\d{2}\.\d$/;
-
- if (apiVersion && apiVersionRegex.test(apiVersion)) {
- options.version = apiVersion;
- } else {
- options.version = '47.0';
- }
- console.log('Using Salesforce API version:', options.version);
-
- return new jsforce.Connection(options);
-}
-
-async function createBasicAuthConnection(state) {
- const { loginUrl, username, password, securityToken } = state.configuration;
-
- const connection = getConnection(state, { loginUrl });
-
- await connection
- .login(username, securityToken ? password + securityToken : password)
- .catch(e => {
- console.error(`Failed to connect to salesforce as ${username}`);
- throw e;
- });
-
- console.info(`Connected to salesforce as ${username}.`);
-
- return {
- ...state,
- connection,
- };
-}
-
-function createAccessTokenConnection(state) {
- const { instance_url, access_token } = state.configuration;
-
- const connection = getConnection(state, {
- instanceUrl: instance_url,
- accessToken: access_token,
- });
-
- console.log(`Connected with ${connection._sessionType} session type`);
-
- return {
- ...state,
- connection,
- };
-}
-
-/**
- * Creates a connection to Salesforce using Basic Auth or OAuth.
- * @function createConnection
- * @private
- * @param {State} state - Runtime state.
- * @returns {State}
- */
-function createConnection(state) {
- if (state.connection) {
- return state;
- }
-
- const { access_token } = state.configuration;
-
- return access_token
- ? createAccessTokenConnection(state)
- : createBasicAuthConnection(state);
-}
-
-/**
- * Executes an operation.
- * @function
- * @param {Operation} operations - Operations
- * @returns {State}
- */
-export function execute(...operations) {
- const initialState = {
- logger: {
- info: console.info.bind(console),
- debug: console.log.bind(console),
- },
- references: [],
- data: null,
- configuration: {},
- };
+ const [resolvedSObjectName, resolvedRecords] = expandReferences(
+ state,
+ sObjectName,
+ records
+ );
+ console.info(`Updating ${resolvedSObjectName}`, resolvedRecords);
- return state => {
- // Note: we no longer need `steps` anymore since `commonExecute`
- // takes each operation as an argument.
- return commonExecute(
- loadAnyAscii,
- createConnection,
- ...flatten(operations),
- cleanupState
- )({ ...initialState, ...state });
+ return connection
+ .update(resolvedSObjectName, resolvedRecords)
+ .then(function (result) {
+ console.log('Result : ' + JSON.stringify(result));
+ return composeNextState(state, result);
+ });
};
}
-/**
- * Removes unserializable keys from the state.
- * @example
- * cleanupState(state)
- * @function
- * @param {State} state
- * @returns {State}
- */
-function cleanupState(state) {
- delete state.connection;
- return state;
-}
-
-/**
- * Flattens an array of operations.
- * @example
- * steps(
- * createIf(params),
- * update(params)
- * )
- * @function
- * @returns {array}
- */
-export function steps(...operations) {
- return flatten(operations);
-}
/**
* Transliterates unicode characters to their best ASCII representation
@@ -921,20 +622,18 @@ export function toUTF8(input) {
* method: 'POST',
* json: { inputs: [{}] },
* });
- * @param {string} url - Relative or absolute URL to request from
- * @param {object} options - Request options
+ * @param {string} url - Relative to request from
+ * @param {object} options - The options for the request.
* @param {string} [options.method=GET] - HTTP method to use. Defaults to GET
* @param {object} [options.headers] - Object of request headers
- * @param {object} [options.json] - A JSON Object request body
+ * @param {object} [options.json] - A JSON object to send as the request body.
* @param {string} [options.body] - HTTP body (in POST/PUT/PATCH methods)
- * @param {function} callback - A callback to execute once the request is complete
* @returns {Operation}
*/
-
-export function request(path, options, callback = s => s) {
+export function request(path, options = {}) {
return async state => {
const { connection } = state;
- const [resolvedPath, resolvedOptions] = newExpandReferences(
+ const [resolvedPath, resolvedOptions] = expandReferences(
state,
path,
options
@@ -952,20 +651,45 @@ export function request(path, options, callback = s => s) {
const result = await connection.request(requestOptions);
- const nextState = composeNextState(state, result);
-
- return callback(nextState);
+ return composeNextState(state, result);
};
}
-// Note that we expose the entire axios package to the user here.
-import axios from 'axios';
-export { axios };
+/**
+ * Retrieves a Salesforce sObject(s).
+ * @public
+ * @example
+ * retrieve('ContentVersion', '0684K0000020Au7QAE/VersionData');
+ * @function
+ * @param {string} sObjectName - The sObject to retrieve
+ * @param {string} id - The id of the record
+ * @returns {Operation}
+ */
+export function retrieve(sObjectName, id) {
+ return state => {
+ const { connection } = state;
+
+ const [resolvedSObjectName, resolvedId] = expandReferences(
+ state,
+ sObjectName,
+ id
+ );
+
+ console.log(
+ `Retrieving data for sObject '${resolvedSObjectName}' with Id '${resolvedId}'`
+ );
+ return connection
+ .sobject(resolvedSObjectName)
+ .retrieve(resolvedId)
+ .then(result => {
+ return composeNextState(state, result);
+ });
+ };
+}
export {
alterState,
arrayToString,
- beta,
chunk,
combine,
dataPath,
diff --git a/packages/salesforce/src/Utils.js b/packages/salesforce/src/Utils.js
new file mode 100644
index 000000000..62e47e85b
--- /dev/null
+++ b/packages/salesforce/src/Utils.js
@@ -0,0 +1,136 @@
+import jsforce from 'jsforce';
+
+function getConnection(state, options) {
+ const { apiVersion } = state.configuration;
+
+ const apiVersionRegex = /^\d{2}\.\d$/;
+
+ if (apiVersion && apiVersionRegex.test(apiVersion)) {
+ options.version = apiVersion;
+ } else {
+ options.version = '47.0';
+ }
+ console.log('Using Salesforce API version:', options.version);
+
+ return new jsforce.Connection(options);
+}
+
+async function createBasicAuthConnection(state) {
+ const { loginUrl, username, password, securityToken } = state.configuration;
+
+ const connection = getConnection(state, { loginUrl });
+
+ await connection
+ .login(username, securityToken ? password + securityToken : password)
+ .catch(e => {
+ console.error(`Failed to connect to salesforce as ${username}`);
+ throw e;
+ });
+
+ console.info(`Connected to salesforce as ${username}.`);
+
+ return {
+ ...state,
+ connection,
+ };
+}
+
+function createAccessTokenConnection(state) {
+ const { instance_url, access_token } = state.configuration;
+
+ const connection = getConnection(state, {
+ instanceUrl: instance_url,
+ accessToken: access_token,
+ });
+
+ console.log(`Connected with ${connection._sessionType} session type`);
+
+ return {
+ ...state,
+ connection,
+ };
+}
+
+/**
+ * Creates a connection to Salesforce using Basic Auth or OAuth.
+ * @function createConnection
+ * @private
+ * @param {State} state - Runtime state.
+ * @returns {State}
+ */
+export function createConnection(state) {
+ if (state.connection) {
+ return state;
+ }
+
+ const { access_token } = state.configuration;
+
+ return access_token
+ ? createAccessTokenConnection(state)
+ : createBasicAuthConnection(state);
+}
+
+/**
+ * Removes state.connection from state.
+ * @example
+ * removeConnection(state)
+ * @function
+ * @private
+ * @param {State} state
+ * @returns {State}
+ */
+export function removeConnection(state) {
+ delete state.connection;
+ return state;
+}
+
+export async function pollJobResult(conn, job, pollInterval, pollTimeout) {
+ let attempt = 0;
+
+ const maxPollingAttempts = Math.floor(pollTimeout / pollInterval);
+
+ while (attempt < maxPollingAttempts) {
+ // Make an HTTP GET request to check the job status
+ const jobInfo = await conn
+ .request({
+ method: 'GET',
+ url: `/services/data/v${conn.version}/jobs/query/${job.id}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ .catch(error => {
+ console.log('Failed to fetch job information', error);
+ });
+
+ if (jobInfo && jobInfo.state === 'JobComplete') {
+ const response = await conn.request({
+ method: 'GET',
+ url: `/services/data/v${conn.version}/jobs/query/${job.id}/results`,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ console.log('Job result retrieved', response.length);
+ return response;
+ } else {
+ // Handle maxPollingAttempts
+ if (attempt + 1 === maxPollingAttempts) {
+ console.error(
+ 'Maximum polling attempt reached, Please increase pollInterval and pollTimeout'
+ );
+ throw new Error(`Polling time out. Job Id = ${job.id}`);
+ }
+ console.log(
+ `Attempt ${attempt + 1} - Job ${jobInfo.id} is still in ${
+ jobInfo.state
+ }:`
+ );
+ }
+
+ // Wait for the polling interval before the next attempt
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
+ attempt++;
+ }
+}
diff --git a/packages/salesforce/test/Adaptor.test.js b/packages/salesforce/test/Adaptor.test.js
index 46120a1cf..37cb89bb2 100644
--- a/packages/salesforce/test/Adaptor.test.js
+++ b/packages/salesforce/test/Adaptor.test.js
@@ -1,26 +1,10 @@
import chai from 'chai';
import sinon from 'sinon';
-import {
- reference,
- create,
- createIf,
- upsert,
- upsertIf,
- toUTF8,
- execute,
-} from '../src/Adaptor';
+import { create, upsert, toUTF8, execute } from '../src/Adaptor';
const { expect } = chai;
describe('Adaptor', () => {
- describe('reference', () => {
- it('returns the Id of a previous operation', () => {
- let state = { references: [{ id: '12345' }] };
- let Id = reference(0)(state);
- expect(Id).to.eql('12345');
- });
- });
-
describe('create', () => {
it('makes a new sObject', done => {
const fakeConnection = {
@@ -42,60 +26,7 @@ describe('Adaptor', () => {
.then(state => {
expect(spy.args[0]).to.eql([sObject, fields]);
expect(spy.called).to.eql(true);
- expect(state.references[0]).to.eql({ Id: 10 });
- })
- .then(done)
- .catch(done);
- });
- });
-
- describe('createIf', () => {
- it("doesn't create a new sObject if a logical is false", done => {
- const fakeConnection = {
- create: function () {
- return Promise.resolve({ Id: 10 });
- },
- };
- let state = { connection: fakeConnection, references: [] };
-
- let logical = 1 + 1 == 3;
-
- let sObject = 'myObject';
- let fields = { field: 'value' };
-
- let spy = sinon.spy(fakeConnection, 'create');
-
- createIf(logical, sObject, fields)(state);
-
- expect(spy.called).to.eql(false);
- expect(state).to.eql({ connection: fakeConnection, references: [] });
- done();
- });
-
- it('makes a new sObject if a logical is true', done => {
- const fakeConnection = {
- create: function () {
- return Promise.resolve({ Id: 10 });
- },
- };
- let state = { connection: fakeConnection, references: [] };
-
- let logical = 1 + 1 == 2;
-
- let sObject = 'myObject';
- let fields = { field: 'value' };
-
- let spy = sinon.spy(fakeConnection, 'create');
-
- createIf(
- logical,
- sObject,
- fields
- )(state)
- .then(state => {
- expect(spy.args[0]).to.eql([sObject, fields]);
- expect(spy.called).to.eql(true);
- expect(state.references[0]).to.eql({ Id: 10 });
+ expect(state.data).to.eql({ Id: 10 });
})
.then(done)
.catch(done);
@@ -125,40 +56,7 @@ describe('Adaptor', () => {
.then(state => {
expect(spy.args[0]).to.eql([sObject, fields, externalId]);
expect(spy.called).to.eql(true);
- expect(state.references[0]).to.eql({ Id: 10 });
- })
- .then(done)
- .catch(done);
- });
- });
-
- describe('upsertIf', () => {
- it('upserts if a logical is true', done => {
- const fakeConnection = {
- upsert: function () {
- return Promise.resolve({ Id: 10 });
- },
- };
- let state = { connection: fakeConnection, references: [] };
-
- let logical = 1 + 1 == 2;
-
- let sObject = 'myObject';
- let externalId = 'MyExternalId';
- let fields = { field: 'value' };
-
- let spy = sinon.spy(fakeConnection, 'upsert');
-
- upsertIf(
- logical,
- sObject,
- externalId,
- fields
- )(state)
- .then(state => {
- expect(spy.args[0]).to.eql([sObject, fields, externalId]);
- expect(spy.called).to.eql(true);
- expect(state.references[0]).to.eql({ Id: 10 });
+ expect(state.data).to.eql({ Id: 10 });
})
.then(done)
.catch(done);
diff --git a/packages/salesforce/test/metadata/lookup.test.js b/packages/salesforce/test/metadata/lookup.test.js
index 7e8a96d47..f14993b65 100644
--- a/packages/salesforce/test/metadata/lookup.test.js
+++ b/packages/salesforce/test/metadata/lookup.test.js
@@ -15,7 +15,7 @@ describe('Salesforce lookup tests', async () => {
// Unit tests of each query against the cached metadata
describe('upsert', () => {
it('sObject: should list non-system sObject names', () => {
- const results = jp.query(data, queries.upsert.sObject);
+ const results = jp.query(data, queries.upsert.sObjectName);
// Note that there are two sobjects in the model - this should correctly just return 1
expect(results).to.have.lengthOf(1);
expect(results).to.include('vera__Beneficiary__c');
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3d77540fe..491dbe9cb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1624,9 +1624,6 @@ importers:
any-ascii:
specifier: ^0.3.2
version: 0.3.2
- axios:
- specifier: ^0.21.4
- version: 0.21.4
jsforce:
specifier: ^1.11.1
version: 1.11.1