Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
62 changes: 21 additions & 41 deletions source/session.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// :copyright: Copyright (c) 2016 ftrack
import moment from "moment";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc.js";
import loglevel from "loglevel";
import { v4 as uuidV4 } from "uuid";

Expand Down Expand Up @@ -39,6 +40,8 @@ import { convertToIsoString } from "./util/convert_to_iso_string.js";
import { Uploader } from "./uploader.js";
import getSchemaMappingFromSchemas from "./util/get_schema_mapping.js";

dayjs.extend(utc);

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

const ENCODE_DATETIME_FORMAT = "YYYY-MM-DDTHH:mm:ss";
Expand Down Expand Up @@ -86,7 +89,7 @@ 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.decodeDatesAsIso] - Decode dates as ISO strings instead of dayjs objects
* @param {object} [options.ensureSerializableResponse] - Disable normalization of response data
*
* @constructs Session
Expand All @@ -103,7 +106,7 @@ export class Session<
apiEndpoint = "/api",
additionalHeaders = {},
strictApi = false,
decodeDatesAsIso = false,
decodeDatesAsIso = true,
ensureSerializableResponse = false,
}: SessionOptions = {},
) {
Expand Down Expand Up @@ -292,7 +295,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 Down Expand Up @@ -320,19 +323,19 @@ export class Session<
this.serverInformation &&
this.serverInformation.is_timezone_support_enabled
) {
// Ensure that the moment object is in UTC and format
// Ensure that the dayjs 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
// Ensure that the dayjs object is in local time zone and format
// to timezone naive string.
return {
__type__: "datetime",
value: moment(date).local().format(ENCODE_DATETIME_FORMAT),
value: dayjs.utc(date).local().format(ENCODE_DATETIME_FORMAT),
Copy link
Contributor Author

@mertkirbuga mertkirbuga May 28, 2025

Choose a reason for hiding this comment

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

Should we remove this also? @jimmycallin

Copy link
Contributor

Choose a reason for hiding this comment

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

yes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we need an alternative to replace this? Because in this case, when serverInformation and timezone is enabled we will return an object { __type__: "datetime", value:date }. Otherwise, we will return the data instead of date.

Copy link
Contributor

Choose a reason for hiding this comment

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

is_timezone_support_enabled is a separate from recently implemented use_workspace_timezone_enabled - it's an old flag that should always be enabled. we can remove this check and only support when it's true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Still a bit uncertain about my implementation. Can you check this commit f71e035?

};
}

Expand Down Expand Up @@ -375,7 +378,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,7 +389,7 @@ export class Session<
data: any,
identityMap: Data = {},
{
decodeDatesAsIso = false,
decodeDatesAsIso = true,
ensureSerializableResponse = false,
}: {
decodeDatesAsIso?: boolean;
Expand All @@ -408,8 +411,6 @@ export class Session<
}
if (data.__type__ === "datetime" && decodeDatesAsIso) {
return this._decodeDateTimeAsIso(data);
} else if (data.__type__ === "datetime") {
return this._decodeDateTimeAsMoment(data);
}
return this._decodePlainObject(data, identityMap, {
decodeDatesAsIso,
Expand All @@ -422,9 +423,9 @@ 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) {
Expand All @@ -443,27 +444,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 Down Expand Up @@ -496,7 +476,7 @@ export class Session<
collection: any[],
identityMap: Data,
{
decodeDatesAsIso = false,
decodeDatesAsIso = true,
ensureSerializableResponse = false,
}: {
decodeDatesAsIso?: boolean;
Expand Down Expand Up @@ -630,7 +610,7 @@ 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
* @param {string} options.decodeDatesAsIso - Return dates as ISO strings instead of dayjs objects
*
*/
async call<T = ActionResponse<keyof TEntityTypeMap>>(
Expand Down Expand Up @@ -856,7 +836,7 @@ 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.decodeDatesAsIso - Decode dates as ISO strings instead of dayjs 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 +865,7 @@ 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.decodeDatesAsIso - Decode dates as ISO strings instead of dayjs 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 +911,7 @@ 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.decodeDatesAsIso - Decode dates as ISO strings instead of dayjs objects
* @param {object} options.ensureSerializableResponse - Disable normalization of response data
* @return {Promise} Promise which will be resolved with the response.
*/
Expand All @@ -957,7 +937,7 @@ 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.decodeDatesAsIso - Decode dates as ISO strings instead of dayjs objects
* @param {object} options.ensureSerializableResponse - Disable normalization of response data
* @return {Promise} Promise resolved with the response.
*/
Expand All @@ -983,7 +963,7 @@ 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.decodeDatesAsIso - Decode dates as ISO strings instead of dayjs 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
65 changes: 10 additions & 55 deletions test/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { beforeAll, describe, it, expect } from "vitest";

import { v4 as uuidV4 } from "uuid";
import loglevel from "loglevel";
import moment from "moment";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc.js";
import {
ServerPermissionDeniedError,
ServerValidationError,
Expand All @@ -18,6 +19,8 @@ import { getExampleQuery, getInitialSessionQuery, server } from "./server.js";
import { HttpResponse, type PathParams, http } from "msw";
import type { QueryResponse, Data } from "../source/types.js";

dayjs.extend(utc);

const logger = loglevel.getLogger("test_session");
logger.setLevel("debug");

Expand Down Expand Up @@ -128,16 +131,6 @@ describe("Session", () => {
return expect((await headers).get("ftrack-strict-api")).toEqual("true");
});

it("Should allow querying with datetimes decoded as moment objects (default)", async () => {
const result = await session.query(
"select name, created_at from Task limit 1",
);
expect(result.data[0].created_at).toBeInstanceOf(moment);
expect(result.data[0].created_at.toISOString()).toEqual(
"2022-10-10T10:12:09.000Z",
);
});

it("Should allow querying with datetimes decoded as ISO objects", async () => {
const result = await session.query(
"select name, created_at from Task limit 1",
Expand All @@ -160,30 +153,6 @@ describe("Session", () => {
);
expect(result.data[0].created_at).toEqual("2022-10-10T10:12:09.000Z");
});
it("Should allow overriding session decodeDatesAsIso when querying", async () => {
const decodeDatesAsIsoSession = new Session(
credentials.serverUrl,
credentials.apiUser,
credentials.apiKey,
{
decodeDatesAsIso: true,
},
);
await decodeDatesAsIsoSession.initializing;
const result = await decodeDatesAsIsoSession.query(
"select name, created_at from Task limit 1",
{ decodeDatesAsIso: false },
);
expect(result.data[0].created_at).toBeInstanceOf(moment);
expect(result.data[0].created_at.toISOString()).toEqual(
"2022-10-10T10:12:09.000Z",
);
const result2 = await session.query(
"select name, created_at from Task limit 1",
{ decodeDatesAsIso: true },
);
expect(result2.data[0].created_at).toEqual("2022-10-10T10:12:09.000Z");
});

it("Should allow querying with datetimes decoded as ISO objects with timezone support disabled", async () => {
server.use(
Expand Down Expand Up @@ -513,8 +482,8 @@ describe("Session", () => {
.then(done);
});

it.skip("Should support ensure with update moment object as criteria", async (done: any) => {
const now = moment();
it.skip("Should support ensure with update dayjs object as criteria", async (done: any) => {
const now = dayjs();

const name = uuidV4();

Expand Down Expand Up @@ -660,8 +629,8 @@ describe("Session", () => {
});

describe("Encoding entities", () => {
it("Should support encoding moment dates", () => {
const now = moment();
it("Should support encoding dayjs dates", () => {
const now = dayjs();

//@ts-ignore - Otherwise internal method used for testing purposes
const output = session.encode([{ foo: now, bar: "baz" }, 12321]);
Expand All @@ -677,8 +646,8 @@ describe("Encoding entities", () => {
12321,
]);
});
it("Should support encoding moment dates to local timezone if timezone support is disabled", async () => {
const now = moment();
it("Should support encoding dayjs dates to local timezone if timezone support is disabled", async () => {
const now = dayjs();
server.use(
http.post(
"http://ftrack.test/api",
Expand Down Expand Up @@ -834,20 +803,6 @@ describe("Encoding entities", () => {
expect(data[2].status.state.short).toEqual("DONE");
});

it("Should support decoding datetime as moment (default)", () => {
const now = moment();

//@ts-ignore - Otherwise internal method used for testing purposes
const output = session.decode({
foo: {
__type__: "datetime",
value: now.toISOString(),
},
});
expect(output.foo).toBeInstanceOf(moment);
expect(output.foo.toISOString()).toEqual(now.toISOString());
});

it("Should support decoding datetime as ISO string", () => {
const now = new Date();

Expand Down
3 changes: 1 addition & 2 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ export default defineConfig({
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ["moment", "uuid", "loglevel", "isomorphic-ws"],
external: ["uuid", "loglevel", "isomorphic-ws"],
output: {
globals: {
"ftrack-javascript-api": "ftrack",
moment: "moment",
uuid: "uuid",
loglevel: "log",
"isomorphic-ws": "WebSocket",
Expand Down
Loading