Skip to content

feat: Remove moment js #302

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

Merged
merged 7 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
"homepage": "http://ftrack.com",
"dependencies": {
"loglevel": "^1.9.2",
"moment": "^2.30.1",
"uuid": "^11.1.0"
},
"peerDependencies": {
Expand Down
100 changes: 8 additions & 92 deletions source/session.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// :copyright: Copyright (c) 2016 ftrack
import moment from "moment";
import loglevel from "loglevel";
import { v4 as uuidV4 } from "uuid";

Expand Down Expand Up @@ -41,8 +40,6 @@ import getSchemaMappingFromSchemas from "./util/get_schema_mapping.js";

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

const ENCODE_DATETIME_FORMAT = "YYYY-MM-DDTHH:mm:ss";

/**
* ftrack API session
* @class Session
Expand All @@ -64,7 +61,6 @@ export class Session<
serverInformation?: QueryServerInformationResponse;
serverVersion?: string;
private ensureSerializableResponse: boolean;
private decodeDatesAsIso: boolean;
private schemasPromise?: Promise<Schema<TEntityTypeMap>[]>;
private serverInformationPromise?: Promise<ServerInformation>;
private serverInformationValues?: string[];
Expand All @@ -86,7 +82,6 @@ export class Session<
* @param {string} [options.apiEndpoint=/api] - API endpoint.
* @param {object} [options.headers] - Additional headers to send with the request
* @param {object} [options.strictApi] - Turn on strict API mode
* @param {object} [options.decodeDatesAsIso] - Decode dates as ISO strings instead of moment objects
* @param {object} [options.ensureSerializableResponse] - Disable normalization of response data
*
* @constructs Session
Expand All @@ -103,7 +98,6 @@ export class Session<
apiEndpoint = "/api",
additionalHeaders = {},
strictApi = false,
decodeDatesAsIso = false,
ensureSerializableResponse = false,
}: SessionOptions = {},
) {
Expand Down Expand Up @@ -181,15 +175,6 @@ export class Session<
this.clientToken = `ftrack-javascript-api--${uuidV4()}`;
}

// Always include is_timezone_support_enabled as required by API.
// TODO: Remove this in next major.
if (
serverInformationValues &&
!serverInformationValues.includes("is_timezone_support_enabled")
) {
serverInformationValues.push("is_timezone_support_enabled");
}

// TODO: remove these operations from session initialization in next major
const operations: [
operation.QueryServerInformationOperation,
Expand All @@ -202,8 +187,6 @@ export class Session<
{ action: "query_schemas" },
];

this.decodeDatesAsIso = decodeDatesAsIso;

/**
* By default the API server will return normalized responses, and we denormalize them in the client.
* This might cause cyclical references in the response data, making it non-JSON serializable.
Expand Down Expand Up @@ -292,7 +275,7 @@ export class Session<
/**
* Return encoded *data* as JSON string.
*
* This will translate date, moment, and dayjs objects into ISO8601 string representation in UTC.
* This will translate date and dayjs objects into ISO8601 string representation in UTC.
*
* @private
* @param {*} data The data to encode.
Expand All @@ -316,23 +299,9 @@ export class Session<

const date = convertToIsoString(data);
if (date) {
if (
this.serverInformation &&
this.serverInformation.is_timezone_support_enabled
) {
// Ensure that the moment object is in UTC and format
// to timezone naive string.
return {
__type__: "datetime",
value: date,
};
}

// Ensure that the moment object is in local time zone and format
// to timezone naive string.
return {
__type__: "datetime",
value: moment(date).local().format(ENCODE_DATETIME_FORMAT),
value: date,
};
}

Expand Down Expand Up @@ -375,7 +344,7 @@ export class Session<
* de-duplicated in the back end and point them to a single object in
* *identityMap*.
*
* datetime objects will be converted to timezone-aware moment objects.
* datetime objects will be converted to timezone-aware dayjs objects.
*
* @private
* @param {*} data The data to decode.
Expand All @@ -386,33 +355,26 @@ export class Session<
data: any,
identityMap: Data = {},
{
decodeDatesAsIso = false,
ensureSerializableResponse = false,
}: {
decodeDatesAsIso?: boolean;
ensureSerializableResponse?: boolean;
} = {},
): any {
if (Array.isArray(data)) {
return this._decodeArray(data, identityMap, {
decodeDatesAsIso,
ensureSerializableResponse,
});
}
if (!!data && typeof data === "object") {
if (data.__entity_type__ && !ensureSerializableResponse) {
return this._mergeEntity(data, identityMap, {
decodeDatesAsIso,
ensureSerializableResponse,
});
}
if (data.__type__ === "datetime" && decodeDatesAsIso) {
if (data.__type__ === "datetime") {
return this._decodeDateTimeAsIso(data);
} else if (data.__type__ === "datetime") {
return this._decodeDateTimeAsMoment(data);
}
return this._decodePlainObject(data, identityMap, {
decodeDatesAsIso,
ensureSerializableResponse,
});
}
Expand All @@ -422,19 +384,14 @@ export class Session<
/**
* Decode datetime *data* into ISO 8601 strings.
*
* Translate objects with __type__ equal to 'datetime' into moment
* Translate objects with __type__ equal to 'datetime' into dayjs
* datetime objects. If time zone support is enabled on the server the date
* will be assumed to be UTC and the moment will be in utc.
* will be assumed to be UTC and the dayjs will be in utc.
* @private
*/
private _decodeDateTimeAsIso(data: any) {
let dateValue = data.value;
if (
this.serverInformation &&
this.serverInformation.is_timezone_support_enabled &&
!dateValue.endsWith("Z") &&
!dateValue.includes("+")
) {
if (!dateValue.endsWith("Z") && !dateValue.includes("+")) {
// Server responds with timezone naive strings, add Z to indicate UTC.
// If the string somehow already contains a timezone offset, do not add Z.
dateValue += "Z";
Expand All @@ -443,27 +400,6 @@ export class Session<
return new Date(dateValue).toISOString();
}

/**
* Decode datetime *data* into moment objects.
*
* Translate objects with __type__ equal to 'datetime' into moment
* datetime objects. If time zone support is enabled on the server the date
* will be assumed to be UTC and the moment will be in utc.
* @private
*/
private _decodeDateTimeAsMoment(data: any) {
if (
this.serverInformation &&
this.serverInformation.is_timezone_support_enabled
) {
// Return date as moment object with UTC set to true.
return moment.utc(data.value);
}

// Return date as local moment object.
return moment(data.value);
}

/**
* Return new object where all values have been decoded.
* @private
Expand All @@ -472,16 +408,13 @@ export class Session<
object: Data,
identityMap: Data,
{
decodeDatesAsIso,
ensureSerializableResponse,
}: {
decodeDatesAsIso?: boolean;
ensureSerializableResponse?: boolean;
} = {},
) {
return Object.keys(object).reduce<Data>((previous, key) => {
previous[key] = this.decode(object[key], identityMap, {
decodeDatesAsIso,
ensureSerializableResponse,
});
return previous;
Expand All @@ -496,16 +429,13 @@ export class Session<
collection: any[],
identityMap: Data,
{
decodeDatesAsIso = false,
ensureSerializableResponse = false,
}: {
decodeDatesAsIso?: boolean;
ensureSerializableResponse?: boolean;
} = {},
): any[] {
return collection.map((item) =>
this.decode(item, identityMap, {
decodeDatesAsIso,
ensureSerializableResponse,
}),
);
Expand All @@ -519,10 +449,8 @@ export class Session<
entity: Data,
identityMap: Data,
{
decodeDatesAsIso,
ensureSerializableResponse,
}: {
decodeDatesAsIso?: boolean;
ensureSerializableResponse?: boolean;
} = {},
) {
Expand All @@ -547,7 +475,6 @@ export class Session<
for (const key in entity) {
if (Object.hasOwn(entity, key)) {
mergedEntity[key] = this.decode(entity[key], identityMap, {
decodeDatesAsIso,
ensureSerializableResponse,
});
}
Expand Down Expand Up @@ -630,7 +557,6 @@ export class Session<
* @param {AbortSignal} options.signal - Abort signal
* @param {string} options.pushToken - push token to associate with the request
* @param {object} options.headers - Additional headers to send with the request
* @param {string} options.decodeDatesAsIso - Return dates as ISO strings instead of moment objects
*
*/
async call<T = ActionResponse<keyof TEntityTypeMap>>(
Expand All @@ -640,7 +566,6 @@ export class Session<
pushToken,
signal,
additionalHeaders = {},
decodeDatesAsIso = this.decodeDatesAsIso,
ensureSerializableResponse = this.ensureSerializableResponse,
}: CallOptions = {},
): Promise<IsTuple<T> extends true ? T : T[]> {
Expand Down Expand Up @@ -690,11 +615,7 @@ export class Session<
throw this.getErrorFromResponse(response);
}
try {
return this.decode(
response,
{},
{ decodeDatesAsIso, ensureSerializableResponse },
);
return this.decode(response, {}, { ensureSerializableResponse });
} catch (reason) {
logger.warn("Server reported error in unexpected format. ", reason);
throw this.getErrorFromResponse({
Expand Down Expand Up @@ -856,7 +777,6 @@ export class Session<
* @param {object} options.abortController - Deprecated in favour of options.signal
* @param {object} options.signal - Abort signal user for aborting requests prematurely
* @param {object} options.headers - Additional headers to send with the request
* @param {object} options.decodeDatesAsIso - Decode dates as ISO strings instead of moment objects
* @param {object} options.ensureSerializableResponse - Disable normalization of response data
* @return {Promise} Promise which will be resolved with an object
* containing action, data and metadata
Expand Down Expand Up @@ -885,7 +805,6 @@ export class Session<
* @param {object} options.abortController - Deprecated in favour of options.signal
* @param {object} options.signal - Abort signal user for aborting requests prematurely
* @param {object} options.headers - Additional headers to send with the request
* @param {object} options.decodeDatesAsIso - Decode dates as ISO strings instead of moment objects
* @param {object} options.ensureSerializableResponse - Disable normalization of response data
* @return {Promise} Promise which will be resolved with an object
* containing data and metadata
Expand Down Expand Up @@ -931,7 +850,6 @@ export class Session<
* @param {Object} options
* @param {string} options.pushToken - push token to associate with the request
* @param {object} options.headers - Additional headers to send with the request
* @param {object} options.decodeDatesAsIso - Decode dates as ISO strings instead of moment objects
* @param {object} options.ensureSerializableResponse - Disable normalization of response data
* @return {Promise} Promise which will be resolved with the response.
*/
Expand All @@ -957,7 +875,6 @@ export class Session<
* @param {Object} options
* @param {string} options.pushToken - push token to associate with the request
* @param {object} options.headers - Additional headers to send with the request
* @param {object} options.decodeDatesAsIso - Decode dates as ISO strings instead of moment objects
* @param {object} options.ensureSerializableResponse - Disable normalization of response data
* @return {Promise} Promise resolved with the response.
*/
Expand All @@ -983,7 +900,6 @@ export class Session<
* @param {Object} options
* @param {string} options.pushToken - push token to associate with the request
* @param {object} options.headers - Additional headers to send with the request
* @param {object} options.decodeDatesAsIso - Decode dates as ISO strings instead of moment objects
* @return {Promise} Promise resolved with the response.
*/
async delete<TEntityType extends keyof TEntityTypeMap>(
Expand Down
7 changes: 3 additions & 4 deletions source/util/convert_to_iso_string.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { Moment } from "moment";
import type dayjs from "dayjs";

/**
Expand All @@ -18,17 +17,17 @@ function isIsoDate(str: string) {

/**
* Converts a string or date object to ISO 6801 compatible string.
* Supports converting regular date objects, or any object that has toISOString() method such as moment or dayjs.
* Supports converting regular date objects, or any object that has toISOString() method such as dayjs.
*
* @param data - string or date object
* @returns ISO 6801 compatible string, or null if invalid date
*/
export function convertToIsoString(
data: string | Date | Moment | ReturnType<typeof dayjs>,
data: string | Date | ReturnType<typeof dayjs>,
) {
if (
data &&
// if this is a date object of type moment or dayjs, or regular date object (all of them has toISOString)
// if this is a date object of type dayjs, or regular date object (all of them has toISOString)
((typeof data !== "string" && typeof data.toISOString === "function") ||
// if it's a ISO string already
(typeof data == "string" && isIsoDate(data)))
Expand Down
8 changes: 0 additions & 8 deletions test/convert_to_iso_string.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// :copyright: Copyright (c) 2022 ftrack

import { convertToIsoString } from "../source/util/convert_to_iso_string.js";
import moment from "moment";
import dayjs from "dayjs";
import { describe, it, expect } from "vitest";

Expand All @@ -27,13 +26,6 @@ describe("convertToIsoString", () => {
expect(converted).toEqual(isoDate);
});

it("should convert moment objects to ISO strings in UTC", () => {
const tzDate = "2023-01-01T01:00:00+01:00";
const isoDate = "2023-01-01T00:00:00.000Z";
const converted = convertToIsoString(moment(tzDate));
expect(converted).toEqual(isoDate);
});

it("should convert dayjs objects to ISO strings", () => {
const tzDate = "2023-01-01T01:00:00+01:00";
const isoDate = "2023-01-01T00:00:00.000Z";
Expand Down
Loading