Skip to content

Commit 950638f

Browse files
authored
feat: Implement asynchronous getSchemas method and prepare for not querying schemas and server information on initialization (#124)
* feat: add getSchemas function in preparation for future breaking change where the schemas aren't loaded on init * make sure it only does one query in case several getSchemas are called concurrently * use private promise variables instead of overloading this.schemas and this.serverInformation * fix: we never stored the promises on initialization * add getServerVersion test * fix test * fix suggestion * remove default value on serverInformationValues * fix documentation * perf: store a schema mapping to speed up lookups
1 parent 1f6c62b commit 950638f

File tree

4 files changed

+111
-10
lines changed

4 files changed

+111
-10
lines changed

source/session.ts

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ import type {
3030
Schema,
3131
SearchOptions,
3232
SearchResponse,
33+
ServerInformation,
3334
SessionOptions,
3435
UpdateResponse,
3536
} from "./types.js";
3637
import { convertToIsoString } from "./util/convert_to_iso_string.js";
3738
import { Uploader } from "./uploader.js";
39+
import getSchemaMappingFromSchemas from "./util/get_schema_mapping.js";
3840

3941
const logger = loglevel.getLogger("ftrack_api");
4042

@@ -54,10 +56,14 @@ export class Session {
5456
clientToken: string | null;
5557
initialized: boolean;
5658
initializing: Promise<Session>;
57-
serverInformation?: Data;
59+
additionalHeaders: Data;
5860
schemas?: Schema[];
61+
serverInformation?: QueryServerInformationResponse;
5962
serverVersion?: string;
60-
additionalHeaders: Data;
63+
private schemasPromise?: Promise<Schema[]>;
64+
private serverInformationPromise?: Promise<ServerInformation>;
65+
private serverInformationValues?: string[];
66+
private schemaMapping?: Record<string, Schema>;
6167

6268
/**
6369
* Construct Session instance with API credentials.
@@ -122,6 +128,8 @@ export class Session {
122128
*/
123129
this.serverUrl = serverUrl;
124130

131+
this.serverInformationValues = serverInformationValues;
132+
125133
/**
126134
* API Endpoint. Specified relative to server URL with leading slash.
127135
* @memberof Session
@@ -164,13 +172,15 @@ export class Session {
164172
}
165173

166174
// Always include is_timezone_support_enabled as required by API.
175+
// TODO: Remove this in next major.
167176
if (
168177
serverInformationValues &&
169178
!serverInformationValues.includes("is_timezone_support_enabled")
170179
) {
171180
serverInformationValues.push("is_timezone_support_enabled");
172181
}
173182

183+
// TODO: remove these operations from session initialization in next major
174184
const operations: operation.Operation[] = [
175185
{
176186
action: "query_server_information",
@@ -187,22 +197,36 @@ export class Session {
187197
*/
188198
this.initialized = false;
189199

200+
const initializingPromise =
201+
this.call<[QueryServerInformationResponse, QuerySchemasResponse]>(
202+
operations
203+
);
204+
190205
/**
191206
* Resolved once session is initialized.
192207
* @memberof Session
193208
* @instance
194209
* @type {Promise}
195210
*/
196-
this.initializing = this.call<
197-
[QueryServerInformationResponse, QuerySchemasResponse]
198-
>(operations).then((responses) => {
211+
this.initializing = initializingPromise.then((responses) => {
212+
// TODO: Make this.serverInformation, this.schemas, and this.serverVersion private in next major
213+
// and require calling getServerInformation, getSchemas, and this.getServerVersion instead.
199214
this.serverInformation = responses[0];
200215
this.schemas = responses[1];
216+
this.schemaMapping = getSchemaMappingFromSchemas(this.schemas);
201217
this.serverVersion = this.serverInformation.version;
202218
this.initialized = true;
203219

204220
return Promise.resolve(this);
205221
});
222+
223+
this.serverInformationPromise = initializingPromise
224+
.then((responses) => responses[0])
225+
.catch(() => ({} as ServerInformation));
226+
227+
this.schemasPromise = initializingPromise
228+
.then((responses) => responses[1])
229+
.catch(() => [] as Schema[]);
206230
}
207231

208232
/**
@@ -211,11 +235,12 @@ export class Session {
211235
* @return {Array|null} List of primary key attributes.
212236
*/
213237
getPrimaryKeyAttributes(entityType: string): string[] | null {
238+
// Todo: make this async in next major
214239
if (!this.schemas) {
215240
logger.warn("Schemas not available.");
216241
return null;
217242
}
218-
const schema = this.schemas.find((item) => item.id === entityType);
243+
const schema = this.schemaMapping?.[entityType];
219244
if (!schema || !schema.primary_key || !schema.primary_key.length) {
220245
logger.warn("Primary key could not be found for: ", entityType);
221246
return null;
@@ -474,6 +499,52 @@ export class Session {
474499
return JSON.stringify(this.encode(operations));
475500
}
476501

502+
/**
503+
* Returns server information for the session, using serverInformationValues as set on session initialization.
504+
* This is cached after the first call, and assumes that the server information will not change during the session.
505+
* @returns Promise with the server information for the session.
506+
*/
507+
async getServerInformation(): Promise<ServerInformation> {
508+
if (!this.serverInformationPromise) {
509+
this.serverInformationPromise = this.call<QueryServerInformationResponse>(
510+
[
511+
{
512+
action: "query_server_information",
513+
values: this.serverInformationValues,
514+
},
515+
]
516+
).then((responses) => responses[0]);
517+
}
518+
519+
return this.serverInformationPromise;
520+
}
521+
522+
/**
523+
* Returns server version for the session, using serverInformationValues as set on session initialization.
524+
* This is cached after the first call, and assumes that the server information will not change during the session.
525+
* @returns Promise with the server version for the session.
526+
*/
527+
async getServerVersion(): Promise<string> {
528+
return (await this.getServerInformation()).version;
529+
}
530+
531+
/**
532+
* Returns the API schemas for the session.
533+
* This is cached after the first call, and assumes that the schemas will not change during the session.
534+
* @returns Promise with the API schemas for the session.
535+
*/
536+
async getSchemas(): Promise<Schema[]> {
537+
if (!this.schemasPromise) {
538+
this.schemasPromise = this.call<QuerySchemasResponse>([
539+
{ action: "query_schemas" },
540+
]).then((responses) => {
541+
this.schemaMapping = getSchemaMappingFromSchemas(responses[0]);
542+
return responses[0];
543+
});
544+
}
545+
return this.schemasPromise;
546+
}
547+
477548
/**
478549
* Call API with array of operation objects in *operations*.
479550
*
@@ -509,7 +580,9 @@ export class Session {
509580
decodeDatesAsIso = false,
510581
}: CallOptions = {}
511582
): Promise<IsTuple<T> extends true ? T : T[]> {
512-
await this.initializing;
583+
if (this.initializing) {
584+
await this.initializing;
585+
}
513586
const url = `${this.serverUrl}${this.apiEndpoint}`;
514587

515588
try {
@@ -688,7 +761,8 @@ export class Session {
688761
* @return {Object|null} Schema definition
689762
*/
690763
getSchema(schemaId: string): Schema | null {
691-
const schema = this.schemas?.find((s) => s.id === schemaId);
764+
// TODO: make this async in next major
765+
const schema = this.schemaMapping?.[schemaId];
692766
return schema ?? null;
693767
}
694768

source/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ export interface ResetRemoteResponse {
6868
data: Data;
6969
}
7070
export type QuerySchemasResponse = Schema[];
71-
export interface QueryServerInformationResponse {
71+
72+
export type QueryServerInformationResponse = ServerInformation;
73+
export interface ServerInformation {
7274
custom_widget?: Data;
7375
default_colors?: string[];
7476
is_nested_subqueries_enabled?: boolean;
@@ -91,7 +93,7 @@ export interface QueryServerInformationResponse {
9193
username?: string;
9294
};
9395
product?: Data;
94-
version?: string;
96+
version: string;
9597
schema_hash?: string;
9698
storage_scenario?: Data;
9799
[key: string]: any;

source/util/get_schema_mapping.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// :copyright: Copyright (c) 2023 ftrack
2+
3+
import { Schema } from "../types.js";
4+
5+
export default function getSchemaMappingFromSchemas(schemas: Schema[]) {
6+
const schemaMapping = {} as Record<string, Schema>;
7+
for (const schema of schemas) {
8+
schemaMapping[schema.id] = schema;
9+
}
10+
return schemaMapping;
11+
}

test/session.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,20 @@ describe("Session", () => {
547547
})
548548
).rejects.toThrowError("Code must be provided to enable totp.");
549549
});
550+
551+
it("Should support getting schemas with session.getSchemas()", async () => {
552+
expect(await session.getSchemas()).toEqual(querySchemas);
553+
});
554+
555+
it("Should support getting server information with session.getServerInformation()", async () => {
556+
expect(await session.getServerInformation()).toEqual(
557+
queryServerInformation
558+
);
559+
});
560+
561+
it("Should support getting server version with session.getServerVersion()", async () => {
562+
expect(await session.getServerVersion()).toEqual("dev");
563+
});
550564
});
551565

552566
describe("Encoding entities", () => {

0 commit comments

Comments
 (0)