diff --git a/package-lock.json b/package-lock.json index c97dd04..d14c150 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@vacasaoss/ts-force-project", - "version": "3.4.4", + "version": "4.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@vacasaoss/ts-force-project", - "version": "3.4.4", + "version": "4.0.0", "hasInstallScript": true, "license": "ISC" } diff --git a/package.json b/package.json index e4b87ed..adf1635 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vacasaoss/ts-force-project", - "version": "3.4.4", + "version": "4.0.0", "description": "", "scripts": { "postinstall": "cd ts-force && npm install && npm run build && cd ../ts-force-gen && npm install", @@ -25,4 +25,4 @@ "url": "https://github.com/ChuckJonas/ts-force/issues" }, "homepage": "https://github.com/ChuckJonas/ts-force#readme" -} \ No newline at end of file +} diff --git a/ts-force-gen/package-lock.json b/ts-force-gen/package-lock.json index b73f5cf..f01c877 100644 --- a/ts-force-gen/package-lock.json +++ b/ts-force-gen/package-lock.json @@ -1,12 +1,12 @@ { "name": "@vacasaoss/ts-force-gen", - "version": "3.4.4", + "version": "4.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@vacasaoss/ts-force-gen", - "version": "3.4.4", + "version": "4.0.0", "license": "BSD-3-Clause", "dependencies": { "@salesforce/core": "^2.12.3", diff --git a/ts-force-gen/package.json b/ts-force-gen/package.json index c0cd74c..9c379cc 100644 --- a/ts-force-gen/package.json +++ b/ts-force-gen/package.json @@ -1,6 +1,6 @@ { "name": "@vacasaoss/ts-force-gen", - "version": "3.4.4", + "version": "4.0.0", "description": "Code generation for ts-force", "main": "build/index.js", "typings": "build/index.d.ts", @@ -63,4 +63,4 @@ "ts-morph": "^2.1.0", "tslib": "^2.0.3" } -} \ No newline at end of file +} diff --git a/ts-force/package-lock.json b/ts-force/package-lock.json index 5a6ec6c..979260e 100644 --- a/ts-force/package-lock.json +++ b/ts-force/package-lock.json @@ -1,12 +1,12 @@ { "name": "@vacasaoss/ts-force", - "version": "3.4.4", + "version": "4.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@vacasaoss/ts-force", - "version": "3.4.4", + "version": "4.0.0", "license": "BSD-3-Clause", "dependencies": { "@types/cometd": "^4.0.4", diff --git a/ts-force/package.json b/ts-force/package.json index d599107..c034952 100644 --- a/ts-force/package.json +++ b/ts-force/package.json @@ -1,6 +1,6 @@ { "name": "@vacasaoss/ts-force", - "version": "3.4.4", + "version": "4.0.0", "description": "a typescript client for connecting with salesforce APIs", "main": "build/index.js", "typings": "build/index.d.ts", @@ -57,4 +57,4 @@ "reflect-metadata": "^0.1.10", "tslib": "^1.6.0" } -} \ No newline at end of file +} diff --git a/ts-force/src/rest/restHeader.ts b/ts-force/src/rest/restHeader.ts new file mode 100644 index 0000000..8beebf4 --- /dev/null +++ b/ts-force/src/rest/restHeader.ts @@ -0,0 +1,8 @@ +export type RequestHeadersInput = { + 'Sforce-Auto-Assign'?: boolean; + 'If-Modified-Since'?: Date; + 'If-Unmodified-Since'?: Date; +}; +export type GeneralRequestHeadersInput = RequestHeadersInput | Record; + +export const ConditionalRequestHeaders = ['If-Match', 'If-None-Match', 'If-Modified-Since', 'If-Unmodified-Since']; diff --git a/ts-force/src/rest/restObject.ts b/ts-force/src/rest/restObject.ts index f729ff4..f30607b 100644 --- a/ts-force/src/rest/restObject.ts +++ b/ts-force/src/rest/restObject.ts @@ -10,11 +10,10 @@ import { import { Rest } from './rest'; import { getSFieldProps, SalesforceFieldType, SFieldProperties } from './sObjectDecorators'; import { SObject } from './sObject'; -import { CompositeError, StandardRestError } from './errors'; +import { CompositeError, ConditionalError, StandardRestError } from './errors'; import { FieldProps } from '..'; import { CalendarDate, calendarToString, getCalendarDate } from '../utils/calendarDate'; -import { AxiosRequestHeaders, AxiosResponse } from 'axios'; -import { STATUS_CODES } from 'http'; +import { ConditionalRequestHeaders, GeneralRequestHeadersInput } from './restHeader'; export interface DMLResponse { id: string; @@ -229,12 +228,7 @@ export abstract class RestObject extends SObject { * @returns {Promise} * @memberof RestObject */ - public async update(opts?: { - refresh?: boolean; - sendAllFields?: boolean; - ifModifiedSince?: Date; - ifUnmodifiedSince?: Date; - }): Promise { + public async update(opts?: { refresh?: boolean; sendAllFields?: boolean; headers?: GeneralRequestHeadersInput }): Promise { opts = opts || {}; if (this.id == null) { throw new Error('Must have Id to update!'); @@ -242,14 +236,22 @@ export abstract class RestObject extends SObject { if (opts.refresh === true) { return this.updateComposite(opts.sendAllFields); } else { - const headers = {}; - if (opts.ifModifiedSince) headers['If-Modified-Since'] = opts.ifModifiedSince.toUTCString() - if (opts.ifUnmodifiedSince) headers['If-Unmodified-Since'] = opts.ifUnmodifiedSince.toUTCString() + const headers = this.convertValuesToStrings(opts.headers); const response = await this._client.request.patch( `${this.attributes.url}/${this.id}`, this.toJson({ dmlMode: opts.sendAllFields ? 'update' : 'update_modified_only' }), { headers } ); + + // Check if it contains Conditional Request Headers and deal with errors + const containsConditionalHeader = Object.keys(headers).some((key) => ConditionalRequestHeaders.includes(key)); + if (containsConditionalHeader && (response.status === 304 || response.status === 412)) { + const problematicHeaders = Object.keys(headers) + .filter((key) => ConditionalRequestHeaders.includes(key)) + .join(', '); + throw new ConditionalError(`Conditions not met. Headers: ${problematicHeaders}`); + } + this._modified.clear(); } return this; @@ -497,4 +499,18 @@ export abstract class RestObject extends SObject { throw e; } } + + private convertValuesToStrings(headerInput: GeneralRequestHeadersInput) { + const headers = {}; + for (const key in headerInput) { + const value = headerInput[key]; + if (value instanceof Date) { + headerInput[key] = (value as Date).toUTCString(); + } else if (typeof value === 'boolean') { + headerInput[key] = value.toString(); + } + headers[key] = headerInput[key]; + } + return headers; + } } diff --git a/ts-force/src/test/rest/restObjects.spec.ts b/ts-force/src/test/rest/restObjects.spec.ts index 7e7c667..6ddd330 100644 --- a/ts-force/src/test/rest/restObjects.spec.ts +++ b/ts-force/src/test/rest/restObjects.spec.ts @@ -175,7 +175,7 @@ describe('Generated Classes', () => { await acc.delete(); }) - it('handle ifModifiedSince', async () => { + it('handle multiple headers', async () => { const modifiedDate = new Date(); let acc = new Account({ name: 'account' @@ -183,29 +183,20 @@ describe('Generated Classes', () => { await acc.insert(); //@ts-expect-error acc._client.request.interceptors.request.use((config) => { - if (config.method === 'patch') - expect(config.headers["If-Modified-Since"]).to.equal(modifiedDate.toUTCString()); + if (config.method === 'patch') expect(config.headers['Sforce-Auto-Assign']).to.equal('true'); + if (config.method === 'patch') expect(config.headers['Sforce-Mru']).to.equal('updateMru=true'); + if (config.method === 'patch') expect(config.headers['If-Modified-Since']).to.equal(new Date().toUTCString()); return config }); acc.name = "account2"; - await acc.update({ refresh: false, ifModifiedSince: new Date()}); - await acc.delete(); - }) - - it('handle ifUmodifiedSince', async () => { - const modifiedDate = new Date(); - let acc = new Account({ - name: 'account' + await acc.update({ + refresh: false, + headers: { + 'Sforce-Auto-Assign': true, + 'If-Modified-Since': new Date(), + 'Sforce-Mru': 'updateMru=true', + }, }); - await acc.insert(); - //@ts-expect-error - acc._client.request.interceptors.request.use((config) => { - if (config.method === 'patch') - expect(config.headers["If-Unmodified-Since"]).to.equal(modifiedDate.toUTCString()); - return config - }); - acc.name = "account2"; - await acc.update({ refresh: false, ifUnmodifiedSince: new Date()}); await acc.delete(); }) @@ -489,4 +480,4 @@ describe('Generated Classes', () => { await c.delete(); }); -}); +}); \ No newline at end of file