Skip to content

Commit 4dce809

Browse files
gismyajimmycallin
andauthored
feat: Added response types for different actions (#83)
* Added types for all action responses and general type improvements * More precise return type for createComponent * Added generic types to ensure, query, search, create, update and createComponent --------- Co-authored-by: Jimmy Callin <jimmy.callin@ftrack.com>
1 parent 25475aa commit 4dce809

File tree

3 files changed

+250
-112
lines changed

3 files changed

+250
-112
lines changed

source/project_schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import * as operation from "./operation";
44
import { Session } from "./session";
5-
5+
import { Data, QueryResponse } from "./types";
66
/**
77
* Project schema namespace
88
* @namespace project_schema
@@ -71,7 +71,7 @@ export function getStatuses(
7171
)
7272
);
7373

74-
response = session.call(operations);
74+
response = session.call<QueryResponse<Data>>(operations);
7575
response = response.then((results) => {
7676
// Since the operations where performed in one batched call,
7777
// the result will be merged into a single entity.

source/session.ts

Lines changed: 78 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,25 @@ import {
1515
import { SERVER_LOCATION_ID } from "./constant";
1616

1717
import normalizeString from "./util/normalize_string";
18-
import { Data } from "./types";
18+
import type {
19+
ActionResponse,
20+
CallOptions,
21+
CreateComponentOptions,
22+
CreateResponse,
23+
Data,
24+
DeleteResponse,
25+
GetUploadMetadataResponse,
26+
IsTuple,
27+
MutationOptions,
28+
QueryOptions,
29+
QueryResponse,
30+
QueryServerInformationResponse,
31+
ResponseError,
32+
SearchOptions,
33+
SearchResponse,
34+
SessionOptions,
35+
UpdateResponse,
36+
} from "./types";
1937
import { convertToIsoString } from "./util/convert_to_iso_string";
2038

2139
const logger = loglevel.getLogger("ftrack_api");
@@ -42,75 +60,6 @@ function splitFileExtension(fileName: string) {
4260
return [basename, extension];
4361
}
4462

45-
export interface EventHubOptions {
46-
applicationId?: string;
47-
}
48-
49-
export interface SessionOptions {
50-
autoConnectEventHub?: boolean;
51-
serverInformationValues?: string[];
52-
eventHubOptions?: EventHubOptions;
53-
clientToken?: string;
54-
apiEndpoint?: string;
55-
additionalHeaders?: Data;
56-
strictApi?: boolean;
57-
}
58-
59-
export interface CreateComponentOptions {
60-
name?: string;
61-
data?: Data;
62-
onProgress?: (progress: number) => unknown;
63-
xhr?: XMLHttpRequest;
64-
onAborted?: () => unknown;
65-
}
66-
67-
export interface Entity {
68-
id: string;
69-
__entity_type__: string;
70-
}
71-
72-
export interface SearchOptions {
73-
expression: string;
74-
entityType: string;
75-
terms?: string[];
76-
contextId?: string;
77-
objectTypeIds?: string[];
78-
}
79-
80-
export interface Response<T> {
81-
url?: any;
82-
headers?: any;
83-
action: string;
84-
metadata: {
85-
next: {
86-
offset: number | null;
87-
};
88-
};
89-
data: T[];
90-
}
91-
92-
export interface ResponseError {
93-
exception: string;
94-
content: string;
95-
error_code?: string;
96-
error?: Data;
97-
}
98-
99-
export interface MutationOptions {
100-
pushToken?: string;
101-
additionalHeaders?: Data;
102-
decodeDatesAsIso?: boolean;
103-
}
104-
105-
export interface QueryOptions {
106-
abortController?: AbortController;
107-
signal?: AbortSignal;
108-
additionalHeaders?: Data;
109-
decodeDatesAsIso?: boolean;
110-
}
111-
112-
export interface CallOptions extends MutationOptions, QueryOptions {}
113-
11463
/**
11564
* ftrack API session
11665
* @class Session
@@ -264,7 +213,9 @@ export class Session {
264213
* @instance
265214
* @type {Promise}
266215
*/
267-
this.initializing = this.call(operations).then((responses) => {
216+
this.initializing = this.call<
217+
[QueryServerInformationResponse, QueryResponse]
218+
>(operations).then((responses) => {
268219
this.serverInformation = responses[0];
269220
this.schemas = responses[1];
270221
this.serverVersion = this.serverInformation.version;
@@ -558,6 +509,7 @@ export class Session {
558509
* ServerError
559510
* Generic server errors or network issues
560511
*
512+
* @typeParam T - Either an array of response types to get return type `Tuple<T[0], ..., T[n]>`, or a single response type to get return type T[]. Default is ActionResponse.
561513
* @param {Array} operations - API operations.
562514
* @param {Object} options
563515
* @param {AbortController} options.abortController - Abort controller, deprecated in favor of options.signal
@@ -567,7 +519,7 @@ export class Session {
567519
* @param {string} options.decodeDatesAsIso - Return dates as ISO strings instead of moment objects
568520
*
569521
*/
570-
call(
522+
call<T = ActionResponse>(
571523
operations: operation.Operation[],
572524
{
573525
abortController,
@@ -576,7 +528,7 @@ export class Session {
576528
additionalHeaders = {},
577529
decodeDatesAsIso = false,
578530
}: CallOptions = {}
579-
): Promise<Response<Data>[]> {
531+
): Promise<IsTuple<T> extends true ? T : T[]> {
580532
const url = `${this.serverUrl}${this.apiEndpoint}`;
581533

582534
// Delay call until session is initialized if initialization is in
@@ -677,12 +629,13 @@ export class Session {
677629
*
678630
* Return update or create promise.
679631
*/
680-
ensure(
632+
633+
ensure<T extends Data = Data>(
681634
entityType: string,
682-
data: Data,
683-
identifyingKeys: string[] = []
684-
): Promise<Data> {
685-
let keys = identifyingKeys;
635+
data: T,
636+
identifyingKeys: Array<keyof T> = []
637+
): Promise<T> {
638+
let keys = identifyingKeys as string[];
686639

687640
logger.info(
688641
"Ensuring entity with data using identifying keys: ",
@@ -721,9 +674,9 @@ export class Session {
721674

722675
expression = `${expression} ${criteria.join(" and ")}`;
723676

724-
return this.query(expression).then((response) => {
677+
return this.query<T>(expression).then((response) => {
725678
if (response.data.length === 0) {
726-
return this.create(entityType, data).then(({ data: responseData }) =>
679+
return this.create<T>(entityType, data).then(({ data: responseData }) =>
727680
Promise.resolve(responseData)
728681
);
729682
}
@@ -740,23 +693,23 @@ export class Session {
740693

741694
// Update entity if required.
742695
let updated = false;
743-
Object.keys(data).forEach((key) => {
696+
Object.keys(data).forEach((key: keyof T) => {
744697
if (data[key] !== updateEntity[key]) {
745698
updateEntity[key] = data[key];
746699
updated = true;
747700
}
748701
});
749702

750703
if (updated) {
751-
return this.update(
704+
return this.update<T>(
752705
entityType,
753706
primaryKeys.map((key: string) => updateEntity[key]),
754-
Object.keys(data).reduce<Data>((accumulator, key) => {
707+
Object.keys(data).reduce<T>((accumulator, key: keyof T) => {
755708
if (primaryKeys.indexOf(key) === -1) {
756709
accumulator[key] = data[key];
757710
}
758711
return accumulator;
759-
}, {})
712+
}, {} as T)
760713
).then(({ data: responseData }) => Promise.resolve(responseData));
761714
}
762715

@@ -791,13 +744,15 @@ export class Session {
791744
* @return {Promise} Promise which will be resolved with an object
792745
* containing action, data and metadata
793746
*/
794-
query(expression: string, options: QueryOptions = {}) {
747+
query<T extends Data = Data>(expression: string, options: QueryOptions = {}) {
795748
logger.debug("Query", expression);
796749
const queryOperation = operation.query(expression);
797-
let request = this.call([queryOperation], options).then((responses) => {
798-
const response = responses[0];
799-
return response;
800-
});
750+
let request = this.call<[QueryResponse<T>]>([queryOperation], options).then(
751+
(responses) => {
752+
const response = responses[0];
753+
return response;
754+
}
755+
);
801756

802757
return request;
803758
}
@@ -819,7 +774,7 @@ export class Session {
819774
* @return {Promise} Promise which will be resolved with an object
820775
* containing data and metadata
821776
*/
822-
search(
777+
search<T extends Data = Data>(
823778
{
824779
expression,
825780
entityType,
@@ -844,7 +799,10 @@ export class Session {
844799
contextId,
845800
objectTypeIds,
846801
});
847-
let request = this.call([searchOperation], options).then((responses) => {
802+
let request = this.call<[SearchResponse<T>]>(
803+
[searchOperation],
804+
options
805+
).then((responses) => {
848806
const response = responses[0];
849807
return response;
850808
});
@@ -863,15 +821,20 @@ export class Session {
863821
* @param {object} options.decodeDatesAsIso - Decode dates as ISO strings instead of moment objects
864822
* @return {Promise} Promise which will be resolved with the response.
865823
*/
866-
create(entityType: string, data: Data, options: MutationOptions = {}) {
824+
create<T extends Data = Data>(
825+
entityType: string,
826+
data: T,
827+
options: MutationOptions = {}
828+
) {
867829
logger.debug("Create", entityType, data, options);
868830

869-
let request = this.call([operation.create(entityType, data)], options).then(
870-
(responses) => {
871-
const response = responses[0];
872-
return response;
873-
}
874-
);
831+
let request = this.call<[CreateResponse<T>]>(
832+
[operation.create(entityType, data)],
833+
options
834+
).then((responses) => {
835+
const response = responses[0];
836+
return response;
837+
});
875838

876839
return request;
877840
}
@@ -888,15 +851,15 @@ export class Session {
888851
* @param {object} options.decodeDatesAsIso - Decode dates as ISO strings instead of moment objects
889852
* @return {Promise} Promise resolved with the response.
890853
*/
891-
update(
854+
update<T extends Data = Data>(
892855
type: string,
893856
keys: string[],
894-
data: Data,
857+
data: T,
895858
options: MutationOptions = {}
896859
) {
897860
logger.debug("Update", type, keys, data, options);
898861

899-
const request = this.call(
862+
const request = this.call<[UpdateResponse<T>]>(
900863
[operation.update(type, keys, data)],
901864
options
902865
).then((responses) => {
@@ -921,12 +884,13 @@ export class Session {
921884
delete(type: string, keys: string[], options: MutationOptions = {}) {
922885
logger.debug("Delete", type, keys, options);
923886

924-
let request = this.call([operation.delete(type, keys)], options).then(
925-
(responses) => {
926-
const response = responses[0];
927-
return response;
928-
}
929-
);
887+
let request = this.call<[DeleteResponse]>(
888+
[operation.delete(type, keys)],
889+
options
890+
).then((responses) => {
891+
const response = responses[0];
892+
return response;
893+
});
930894

931895
return request;
932896
}
@@ -994,10 +958,12 @@ export class Session {
994958
* @return {Promise} Promise resolved with the response when creating
995959
* Component and ComponentLocation.
996960
*/
997-
createComponent(
961+
createComponent<T extends Data = Data>(
998962
file: Blob,
999963
options: CreateComponentOptions = {}
1000-
): Promise<Response<Data>[]> {
964+
): Promise<
965+
[CreateResponse<T>, CreateResponse<T>, GetUploadMetadataResponse]
966+
> {
1001967
const componentName = options.name ?? (file as File).name;
1002968

1003969
let normalizedFileName;
@@ -1052,7 +1018,9 @@ export class Session {
10521018
location_id: SERVER_LOCATION_ID,
10531019
};
10541020

1055-
const componentAndLocationPromise = this.call([
1021+
const componentAndLocationPromise = this.call<
1022+
[CreateResponse<T>, CreateResponse<T>, GetUploadMetadataResponse]
1023+
>([
10561024
operation.create("FileComponent", component),
10571025
operation.create("ComponentLocation", componentLocation),
10581026
{

0 commit comments

Comments
 (0)