Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add header input type to support multi headers #8

Merged
merged 16 commits into from
Feb 27, 2024
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vacasaoss/ts-force-project",
"version": "3.4.4",
"version": "3.4.5",
"description": "",
"scripts": {
"postinstall": "cd ts-force && npm install && npm run build && cd ../ts-force-gen && npm install",
Expand Down
4 changes: 2 additions & 2 deletions ts-force-gen/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ts-force-gen/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vacasaoss/ts-force-gen",
"version": "3.4.4",
"version": "3.4.5",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a breaking change? Should we increment the major version?

"description": "Code generation for ts-force",
"main": "build/index.js",
"typings": "build/index.d.ts",
Expand Down
4 changes: 2 additions & 2 deletions ts-force/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ts-force/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vacasaoss/ts-force",
"version": "3.4.4",
"version": "3.4.5",
"description": "a typescript client for connecting with salesforce APIs",
"main": "build/index.js",
"typings": "build/index.d.ts",
Expand Down
30 changes: 30 additions & 0 deletions ts-force/src/rest/restHeader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export type SforceMru = {
updateMru: boolean;
};
export type PackageVersion = {
package: string;
version: string;
};

export type GenericHeader = {
header: string;
value: string;
};

export type RequestHeadersInput = {
'Sforce-Auto-Assign'?: boolean;
'Sforce-Mru'?: SforceMru;
'x-sfdc-packageversion'?: PackageVersion;
'If-Modified-Since'?: Date;
'If-Unmodified-Since'?: Date;
'If-Match'?: string[];
'If-None-Match'?: string[];
'Generic-Header'?: GenericHeader;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Generic-Header just allowing an arbitrary header key/value?

If so, it seems like you would want to potentially support multiple values. It might also be easier to just allow a Record<string, string>.

additionalHeaders?: Record<string, string>;

};

export const ConditionalRequestHeaders: (keyof RequestHeadersInput)[] = [
'If-Match',
'If-None-Match',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never actually used these etag caching headers with Salesforce... Reviewing the docs, I see this statement:

An optional header specifying a comma-delimited list of one or more ETags. This only has an effect when used with Account objects. The request is only processed if the Account’s ETag does not match one of the ETags in the list.

Does it really only work with Account?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the document here, Etag is used for http caching. I guess that’s the reason why it’s said only work with Account. I test with a a case project, and If-Match and If-None-Match can be added on get request but not on patch request.
Screenshot 2024-02-23 at 9 20 14 AM
Screenshot 2024-02-23 at 9 19 46 AM

'If-Modified-Since',
'If-Unmodified-Since',
];
61 changes: 48 additions & 13 deletions ts-force/src/rest/restObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { RequestHeadersInput, ConditionalRequestHeaders, SforceMru, PackageVersion, GenericHeader } from './restHeader';

export interface DMLResponse {
id: string;
Expand Down Expand Up @@ -229,27 +228,29 @@ export abstract class RestObject extends SObject {
* @returns {Promise<void>}
* @memberof RestObject
*/
public async update(opts?: {
refresh?: boolean;
sendAllFields?: boolean;
ifModifiedSince?: Date;
ifUnmodifiedSince?: Date;
}): Promise<this> {
public async update(opts?: { refresh?: boolean; sendAllFields?: boolean; headers?: RequestHeadersInput[] }): Promise<this> {
opts = opts || {};
if (this.id == null) {
throw new Error('Must have Id to update!');
}
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 headerOutput = this.setHeader(opts.headers);
const response = await this._client.request.patch(
`${this.attributes.url}/${this.id}`,
this.toJson({ dmlMode: opts.sendAllFields ? 'update' : 'update_modified_only' }),
{ headers }
{ headers: headerOutput }
);

// Check if it contains Conditional Request Headers and deal with errors
const containsConditionalHeader = Object.keys(opts.headers).some((key) =>
ConditionalRequestHeaders.includes(key as keyof RequestHeadersInput)
);
if (containsConditionalHeader && (response.status === 304 || response.status === 412)) {
throw new ConditionalError('Conditions not met');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to include the header keys in the exception

}

this._modified.clear();
}
return this;
Expand Down Expand Up @@ -497,4 +498,38 @@ export abstract class RestObject extends SObject {
throw e;
}
}

private setHeader(headersInput: RequestHeadersInput[]) {
const headers = {};
for (const header of headersInput) {
for (const [name, value] of Object.entries(header)) {
switch (name) {
case 'Sforce-Auto-Assign':
headers[name] = value.toString();
break;
case 'If-Match':
case 'If-None-Match':
headers[name] = `"${(value as string[]).join('", "')}"`;
break;
case 'If-Modified-Since':
case 'If-Unmodified-Since':
headers[name] = (value as Date).toUTCString();
break;
case 'Sforce-Mru':
headers[name] = `updateMru=${(value as SforceMru).updateMru}`;
break;
case 'x-sfdc-packageversion':
const packageDetail = value as PackageVersion;
headers[`x-sfdc-packageversion-${packageDetail.package}`] = packageDetail.version;
break;
case 'Generic-Header':
default:
const defaultValue = value as GenericHeader;
headers[defaultValue.header] = defaultValue.value;
break;
}
}
}
return headers;
}
}
Loading
Loading