mirror of
https://github.com/logto-io/logto.git
synced 2025-02-03 21:48:55 -05:00
refactor: replace dayjs with date-fns
This commit is contained in:
parent
877eb892c9
commit
15bb084b6e
22 changed files with 193 additions and 133 deletions
|
@ -47,7 +47,6 @@
|
|||
"clean-deep": "^3.4.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"csstype": "^3.0.11",
|
||||
"dayjs": "^1.10.5",
|
||||
"deepmerge": "^4.2.2",
|
||||
"dnd-core": "^16.0.0",
|
||||
"eslint": "^8.21.0",
|
||||
|
@ -97,5 +96,8 @@
|
|||
"stylelint": {
|
||||
"extends": "@silverhand/eslint-config-react/.stylelintrc"
|
||||
},
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc"
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc",
|
||||
"dependencies": {
|
||||
"date-fns": "^2.29.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import type { Nullable } from '@silverhand/essentials';
|
||||
import dayjs from 'dayjs';
|
||||
import { isValid } from 'date-fns';
|
||||
|
||||
type Props = {
|
||||
children: Nullable<string | number>;
|
||||
};
|
||||
|
||||
const DateTime = ({ children }: Props) => {
|
||||
const date = dayjs(children);
|
||||
const date = children && new Date(children);
|
||||
|
||||
if (!children || !date.isValid()) {
|
||||
if (!date || !isValid(date)) {
|
||||
return <span>-</span>;
|
||||
}
|
||||
|
||||
return <span>{date.toDate().toLocaleDateString()}</span>;
|
||||
return <span>{date.toLocaleDateString()}</span>;
|
||||
};
|
||||
|
||||
export default DateTime;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import type { LogDto, User } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
@ -85,7 +84,7 @@ const AuditLogDetails = () => {
|
|||
</div>
|
||||
<div className={styles.infoItem}>
|
||||
<div className={styles.label}>{t('log_details.time')}</div>
|
||||
<div>{dayjs(data.createdAt).toDate().toLocaleString()}</div>
|
||||
<div>{new Date(data.createdAt).toLocaleString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import dayjs from 'dayjs';
|
||||
import { format } from 'date-fns';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -36,7 +36,7 @@ const tickFormatter = new Intl.NumberFormat('en-US', {
|
|||
});
|
||||
|
||||
const Dashboard = () => {
|
||||
const [date, setDate] = useState<string>(dayjs().format('YYYY-MM-DD'));
|
||||
const [date, setDate] = useState<string>(format(Date.now(), 'yyyy-MM-dd'));
|
||||
const { data: totalData, error: totalError } = useSWR<TotalUsersResponse, RequestError>(
|
||||
'/api/dashboard/users/total'
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { ConnectorResponse, ConnectorMetadata, SignInExperience } from '@lo
|
|||
import { AppearanceMode } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import classNames from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
import { format } from 'date-fns';
|
||||
import { useEffect, useMemo, useState, useRef, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
|
@ -195,7 +195,7 @@ const Preview = ({ signInExperience, className }: Props) => {
|
|||
<div className={classNames(styles.device, styles[mode])}>
|
||||
{platform !== 'desktopWeb' && (
|
||||
<div className={styles.topBar}>
|
||||
<div className={styles.time}>{dayjs().format('HH:mm')}</div>
|
||||
<div className={styles.time}>{format(Date.now(), 'HH:mm')}</div>
|
||||
<PhoneInfo />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"@silverhand/essentials": "^1.3.0",
|
||||
"chalk": "^4",
|
||||
"clean-deep": "^3.4.0",
|
||||
"dayjs": "^1.10.5",
|
||||
"date-fns": "^2.29.3",
|
||||
"debug": "^4.3.4",
|
||||
"decamelize": "^5.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
|
|
|
@ -35,10 +35,9 @@ jest.mock('@logto/shared', () => ({
|
|||
const now = Date.now();
|
||||
|
||||
jest.mock(
|
||||
'dayjs',
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
jest.fn(() => () => ({
|
||||
add: jest.fn((delta: number) => new Date(now + delta * 1000)),
|
||||
'date-fns',
|
||||
jest.fn(() => ({
|
||||
add: jest.fn((_: Date, { seconds }: { seconds: number }) => new Date(now + seconds * 1000)),
|
||||
}))
|
||||
);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { CreateApplication, OidcClientMetadata } from '@logto/schemas';
|
||||
import { ApplicationType } from '@logto/schemas';
|
||||
import { adminConsoleApplicationId, demoAppApplicationId } from '@logto/schemas/lib/seeds';
|
||||
import dayjs from 'dayjs';
|
||||
import { add } from 'date-fns';
|
||||
import type { AdapterFactory, AllClientMetadata } from 'oidc-provider';
|
||||
import snakecaseKeys from 'snakecase-keys';
|
||||
|
||||
|
@ -99,7 +99,7 @@ export default function postgresAdapter(modelName: string): ReturnType<AdapterFa
|
|||
modelName,
|
||||
id,
|
||||
payload,
|
||||
expiresAt: dayjs().add(expiresIn, 'second').valueOf(),
|
||||
expiresAt: add(Date.now(), { seconds: expiresIn }).valueOf(),
|
||||
}),
|
||||
find: async (id) => findPayloadById(modelName, id),
|
||||
findByUserCode: async (userCode) => findPayloadByPayloadField(modelName, 'userCode', userCode),
|
||||
|
|
|
@ -7,7 +7,7 @@ import { OidcModelInstances } from '@logto/schemas';
|
|||
import { convertToIdentifiers, convertToTimestamp } from '@logto/shared';
|
||||
import type { Nullable } from '@silverhand/essentials';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import dayjs from 'dayjs';
|
||||
import { add, isBefore } from 'date-fns';
|
||||
import type { ValueExpression } from 'slonik';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
|
@ -30,7 +30,7 @@ const isConsumed = (modelName: string, consumedAt: Nullable<number>): boolean =>
|
|||
return Boolean(consumedAt);
|
||||
}
|
||||
|
||||
return dayjs(consumedAt).add(refreshTokenReuseInterval, 'seconds').isBefore(dayjs());
|
||||
return isBefore(add(consumedAt, { seconds: refreshTokenReuseInterval }), Date.now());
|
||||
};
|
||||
|
||||
const withConsumed = <T>(
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import dayjs from 'dayjs';
|
||||
// The FP version works better for `format()`
|
||||
/* eslint-disable import/no-duplicates */
|
||||
import { endOfDay, subDays } from 'date-fns';
|
||||
import { format } from 'date-fns/fp';
|
||||
/* eslint-enable import/no-duplicates */
|
||||
|
||||
import dashboardRoutes from '@/routes/dashboard';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
@ -8,6 +12,7 @@ const countUsers = jest.fn(async () => ({ count: totalUserCount }));
|
|||
const getDailyNewUserCountsByTimeInterval = jest.fn(
|
||||
async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyNewUserCounts
|
||||
);
|
||||
const formatToQueryDate = format('yyyy-MM-dd');
|
||||
|
||||
jest.mock('@/queries/user', () => ({
|
||||
countUsers: async () => countUsers(),
|
||||
|
@ -83,8 +88,8 @@ describe('dashboardRoutes', () => {
|
|||
it('should call getDailyNewUserCountsByTimeInterval with the time interval (14 days ago 23:59:59.999, today 23:59:59.999]', async () => {
|
||||
await logRequest.get('/dashboard/users/new');
|
||||
expect(getDailyNewUserCountsByTimeInterval).toHaveBeenCalledWith(
|
||||
dayjs().endOf('day').subtract(14, 'day').valueOf(),
|
||||
dayjs().endOf('day').valueOf()
|
||||
subDays(endOfDay(Date.now()), 14).valueOf(),
|
||||
endOfDay(Date.now()).valueOf()
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -105,10 +110,10 @@ describe('dashboardRoutes', () => {
|
|||
});
|
||||
|
||||
describe('GET /dashboard/users/active', () => {
|
||||
const mockToday = '2022-05-30';
|
||||
const mockToday = new Date(2022, 4, 30);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers().setSystemTime(new Date(mockToday));
|
||||
jest.useFakeTimers().setSystemTime(mockToday);
|
||||
});
|
||||
|
||||
it('should fail when the parameter `date` does not match the date regex', async () => {
|
||||
|
@ -117,44 +122,44 @@ describe('dashboardRoutes', () => {
|
|||
});
|
||||
|
||||
it('should call getDailyActiveUserCountsByTimeInterval with the time interval (2022-05-31, 2022-06-30] when the parameter `date` is 2022-06-30', async () => {
|
||||
const targetDate = '2022-06-30';
|
||||
await logRequest.get(`/dashboard/users/active?date=${targetDate}`);
|
||||
const targetDate = new Date(2022, 5, 30);
|
||||
await logRequest.get(`/dashboard/users/active?date=${formatToQueryDate(targetDate)}`);
|
||||
expect(getDailyActiveUserCountsByTimeInterval).toHaveBeenCalledWith(
|
||||
dayjs('2022-05-31').endOf('day').valueOf(),
|
||||
dayjs(targetDate).endOf('day').valueOf()
|
||||
endOfDay(new Date(2022, 4, 31)).valueOf(),
|
||||
endOfDay(targetDate).valueOf()
|
||||
);
|
||||
});
|
||||
|
||||
it('should call getDailyActiveUserCountsByTimeInterval with the time interval (30 days ago, tomorrow] when there is no parameter `date`', async () => {
|
||||
await logRequest.get('/dashboard/users/active');
|
||||
expect(getDailyActiveUserCountsByTimeInterval).toHaveBeenCalledWith(
|
||||
dayjs('2022-04-30').endOf('day').valueOf(),
|
||||
dayjs(mockToday).endOf('day').valueOf()
|
||||
endOfDay(new Date(2022, 3, 30)).valueOf(),
|
||||
endOfDay(mockToday).valueOf()
|
||||
);
|
||||
});
|
||||
|
||||
it('should call countActiveUsersByTimeInterval with correct parameters when the parameter `date` is 2022-06-30', async () => {
|
||||
const targetDate = '2022-06-30';
|
||||
await logRequest.get(`/dashboard/users/active?date=${targetDate}`);
|
||||
const targetDate = new Date(2022, 5, 30);
|
||||
await logRequest.get(`/dashboard/users/active?date=${formatToQueryDate(targetDate)}`);
|
||||
expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
dayjs('2022-06-16').endOf('day').valueOf(),
|
||||
dayjs('2022-06-23').endOf('day').valueOf()
|
||||
endOfDay(new Date(2022, 5, 16)).valueOf(),
|
||||
endOfDay(new Date(2022, 5, 23)).valueOf()
|
||||
);
|
||||
expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
dayjs('2022-06-23').endOf('day').valueOf(),
|
||||
dayjs(targetDate).endOf('day').valueOf()
|
||||
endOfDay(new Date(2022, 5, 23)).valueOf(),
|
||||
endOfDay(targetDate).valueOf()
|
||||
);
|
||||
expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
dayjs('2022-05-01').endOf('day').valueOf(),
|
||||
dayjs('2022-05-31').endOf('day').valueOf()
|
||||
endOfDay(new Date(2022, 4, 1)).valueOf(),
|
||||
endOfDay(new Date(2022, 4, 31)).valueOf()
|
||||
);
|
||||
expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith(
|
||||
4,
|
||||
dayjs('2022-05-31').endOf('day').valueOf(),
|
||||
dayjs(targetDate).endOf('day').valueOf()
|
||||
endOfDay(new Date(2022, 4, 31)).valueOf(),
|
||||
endOfDay(targetDate).valueOf()
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -162,23 +167,23 @@ describe('dashboardRoutes', () => {
|
|||
await logRequest.get('/dashboard/users/active');
|
||||
expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
dayjs('2022-05-16').endOf('day').valueOf(),
|
||||
dayjs('2022-05-23').endOf('day').valueOf()
|
||||
endOfDay(new Date(2022, 4, 16)).valueOf(),
|
||||
endOfDay(new Date(2022, 4, 23)).valueOf()
|
||||
);
|
||||
expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
dayjs('2022-05-23').endOf('day').valueOf(),
|
||||
dayjs(mockToday).endOf('day').valueOf()
|
||||
endOfDay(new Date(2022, 4, 23)).valueOf(),
|
||||
endOfDay(mockToday).valueOf()
|
||||
);
|
||||
expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
dayjs('2022-03-31').endOf('day').valueOf(),
|
||||
dayjs('2022-04-30').endOf('day').valueOf()
|
||||
endOfDay(new Date(2022, 2, 31)).valueOf(),
|
||||
endOfDay(new Date(2022, 3, 30)).valueOf()
|
||||
);
|
||||
expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith(
|
||||
4,
|
||||
dayjs('2022-04-30').endOf('day').valueOf(),
|
||||
dayjs(mockToday).endOf('day').valueOf()
|
||||
endOfDay(new Date(2022, 3, 30)).valueOf(),
|
||||
endOfDay(mockToday).valueOf()
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { dateRegex } from '@logto/core-kit';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import dayjs from 'dayjs';
|
||||
import { endOfDay, format, parse, startOfDay, subDays } from 'date-fns';
|
||||
import { object, string } from 'zod';
|
||||
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
|
@ -12,11 +11,11 @@ import { countUsers, getDailyNewUserCountsByTimeInterval } from '@/queries/user'
|
|||
|
||||
import type { AuthedRouter } from './types';
|
||||
|
||||
const getDateString = (day: Dayjs) => day.format('YYYY-MM-DD');
|
||||
const getDateString = (date: Date | number) => format(date, 'yyyy-MM-dd');
|
||||
|
||||
const indices = (length: number) => [...Array.from({ length }).keys()];
|
||||
|
||||
const lastTimestampOfDay = (day: Dayjs) => day.endOf('day').valueOf();
|
||||
const getEndOfDayTimestamp = (date: Date | number) => endOfDay(date).valueOf();
|
||||
|
||||
export default function dashboardRoutes<T extends AuthedRouter>(router: T) {
|
||||
router.get('/dashboard/users/total', async (ctx, next) => {
|
||||
|
@ -27,11 +26,11 @@ export default function dashboardRoutes<T extends AuthedRouter>(router: T) {
|
|||
});
|
||||
|
||||
router.get('/dashboard/users/new', async (ctx, next) => {
|
||||
const today = dayjs();
|
||||
const today = Date.now();
|
||||
const dailyNewUserCounts = await getDailyNewUserCountsByTimeInterval(
|
||||
// (14 days ago 23:59:59.999, today 23:59:59.999]
|
||||
lastTimestampOfDay(today.subtract(14, 'day')),
|
||||
lastTimestampOfDay(today)
|
||||
getEndOfDayTimestamp(subDays(today, 14)),
|
||||
getEndOfDayTimestamp(today)
|
||||
);
|
||||
|
||||
const last14DaysNewUserCounts = new Map(
|
||||
|
@ -39,15 +38,15 @@ export default function dashboardRoutes<T extends AuthedRouter>(router: T) {
|
|||
);
|
||||
|
||||
const todayNewUserCount = last14DaysNewUserCounts.get(getDateString(today)) ?? 0;
|
||||
const yesterday = today.subtract(1, 'day');
|
||||
const yesterday = subDays(today, 1);
|
||||
const yesterdayNewUserCount = last14DaysNewUserCounts.get(getDateString(yesterday)) ?? 0;
|
||||
const todayDelta = todayNewUserCount - yesterdayNewUserCount;
|
||||
|
||||
const last7DaysNewUserCount = indices(7)
|
||||
.map((index) => getDateString(today.subtract(index, 'day')))
|
||||
.map((index) => getDateString(subDays(today, index)))
|
||||
.reduce((sum, date) => sum + (last14DaysNewUserCounts.get(date) ?? 0), 0);
|
||||
const newUserCountFrom13DaysAgoTo7DaysAgo = indices(7)
|
||||
.map((index) => getDateString(today.subtract(7 + index, 'day')))
|
||||
.map((index) => getDateString(subDays(today, index + 7)))
|
||||
.reduce((sum, date) => sum + (last14DaysNewUserCounts.get(date) ?? 0), 0);
|
||||
const last7DaysDelta = last7DaysNewUserCount - newUserCountFrom13DaysAgoTo7DaysAgo;
|
||||
|
||||
|
@ -75,7 +74,7 @@ export default function dashboardRoutes<T extends AuthedRouter>(router: T) {
|
|||
query: { date },
|
||||
} = ctx.guard;
|
||||
|
||||
const targetDay = date ? dayjs(date) : dayjs(); // Defaults to today
|
||||
const targetDay = date ? parse(date, 'yyyy-MM-dd', startOfDay(Date.now())) : Date.now(); // Defaults to today
|
||||
const [
|
||||
// DAU: Daily Active User
|
||||
last30DauCounts,
|
||||
|
@ -88,39 +87,39 @@ export default function dashboardRoutes<T extends AuthedRouter>(router: T) {
|
|||
] = await Promise.all([
|
||||
getDailyActiveUserCountsByTimeInterval(
|
||||
// (30 days ago 23:59:59.999, target day 23:59:59.999]
|
||||
lastTimestampOfDay(targetDay.subtract(30, 'day')),
|
||||
lastTimestampOfDay(targetDay)
|
||||
getEndOfDayTimestamp(subDays(targetDay, 30)),
|
||||
getEndOfDayTimestamp(targetDay)
|
||||
),
|
||||
countActiveUsersByTimeInterval(
|
||||
// (14 days ago 23:59:59.999, 7 days ago 23:59:59.999]
|
||||
lastTimestampOfDay(targetDay.subtract(14, 'day')),
|
||||
lastTimestampOfDay(targetDay.subtract(7, 'day'))
|
||||
getEndOfDayTimestamp(subDays(targetDay, 14)),
|
||||
getEndOfDayTimestamp(subDays(targetDay, 7))
|
||||
),
|
||||
countActiveUsersByTimeInterval(
|
||||
// (7 days ago 23:59:59.999, target day 23:59:59.999]
|
||||
lastTimestampOfDay(targetDay.subtract(7, 'day')),
|
||||
lastTimestampOfDay(targetDay)
|
||||
getEndOfDayTimestamp(subDays(targetDay, 7)),
|
||||
getEndOfDayTimestamp(targetDay)
|
||||
),
|
||||
countActiveUsersByTimeInterval(
|
||||
// (60 days ago 23:59:59.999, 30 days ago 23:59:59.999]
|
||||
lastTimestampOfDay(targetDay.subtract(60, 'day')),
|
||||
lastTimestampOfDay(targetDay.subtract(30, 'day'))
|
||||
getEndOfDayTimestamp(subDays(targetDay, 60)),
|
||||
getEndOfDayTimestamp(subDays(targetDay, 30))
|
||||
),
|
||||
countActiveUsersByTimeInterval(
|
||||
// (30 days ago 23:59:59.999, target day 23:59:59.999]
|
||||
lastTimestampOfDay(targetDay.subtract(30, 'day')),
|
||||
lastTimestampOfDay(targetDay)
|
||||
getEndOfDayTimestamp(subDays(targetDay, 30)),
|
||||
getEndOfDayTimestamp(targetDay)
|
||||
),
|
||||
]);
|
||||
|
||||
const previousDate = getDateString(targetDay.subtract(1, 'day'));
|
||||
const previousDate = getDateString(subDays(targetDay, 1));
|
||||
const targetDate = getDateString(targetDay);
|
||||
|
||||
const previousDAU = last30DauCounts.find(({ date }) => date === previousDate)?.count ?? 0;
|
||||
const dau = last30DauCounts.find(({ date }) => date === targetDate)?.count ?? 0;
|
||||
|
||||
const dauCurve = indices(30).map((index) => {
|
||||
const dateString = getDateString(targetDay.subtract(29 - index, 'day'));
|
||||
const dateString = getDateString(subDays(targetDay, 29 - index));
|
||||
const count = last30DauCounts.find(({ date }) => date === dateString)?.count ?? 0;
|
||||
|
||||
return { date: dateString, count };
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { User } from '@logto/schemas';
|
||||
import { PasscodeType } from '@logto/schemas';
|
||||
import dayjs from 'dayjs';
|
||||
import { addDays, subDays } from 'date-fns';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import { mockPasswordEncrypted, mockUserWithPassword } from '@/__mocks__';
|
||||
|
@ -15,6 +15,8 @@ const encryptUserPassword = jest.fn(async (password: string) => ({
|
|||
}));
|
||||
const findUserById = jest.fn(async (): Promise<User> => mockUserWithPassword);
|
||||
const updateUserById = jest.fn(async (..._args: unknown[]) => ({ userId: 'id' }));
|
||||
const getYesterdayDate = () => subDays(Date.now(), 1);
|
||||
const getTomorrowDate = () => addDays(Date.now(), 1);
|
||||
|
||||
jest.mock('@/lib/user', () => ({
|
||||
...jest.requireActual('@/lib/user'),
|
||||
|
@ -84,7 +86,7 @@ describe('session -> forgotPasswordRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
userId: 'id',
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowDate().toISOString(),
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
},
|
||||
},
|
||||
|
@ -105,7 +107,7 @@ describe('session -> forgotPasswordRoutes', () => {
|
|||
interactionDetails.mockResolvedValueOnce({
|
||||
result: {
|
||||
verification: {
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowDate().toISOString(),
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
},
|
||||
},
|
||||
|
@ -121,7 +123,7 @@ describe('session -> forgotPasswordRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
userId: 'id',
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowDate().toISOString(),
|
||||
flow: PasscodeType.SignIn,
|
||||
},
|
||||
},
|
||||
|
@ -165,7 +167,7 @@ describe('session -> forgotPasswordRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
userId: 'id',
|
||||
expiresAt: dayjs().subtract(1, 'day').toISOString(),
|
||||
expiresAt: getYesterdayDate().toISOString(),
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
},
|
||||
},
|
||||
|
@ -181,7 +183,7 @@ describe('session -> forgotPasswordRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
userId: 'id',
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowDate().toISOString(),
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
},
|
||||
},
|
||||
|
@ -198,7 +200,7 @@ describe('session -> forgotPasswordRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
userId: 'id',
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowDate().toISOString(),
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable max-lines */
|
||||
import type { User } from '@logto/schemas';
|
||||
import { PasscodeType } from '@logto/schemas';
|
||||
import dayjs from 'dayjs';
|
||||
import { addDays, addSeconds, subDays } from 'date-fns';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import { mockUser } from '@/__mocks__';
|
||||
|
@ -15,6 +15,7 @@ import passwordlessRoutes, { registerRoute, signInRoute } from './passwordless';
|
|||
const insertUser = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
||||
const findUserById = jest.fn(async (): Promise<User> => mockUser);
|
||||
const updateUserById = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
||||
const getTomorrowIsoString = () => addDays(Date.now(), 1).toISOString();
|
||||
|
||||
jest.mock('@/lib/user', () => ({
|
||||
generateUserId: () => 'user1',
|
||||
|
@ -200,7 +201,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
flow: PasscodeType.SignIn,
|
||||
phone: '13000000000',
|
||||
expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(),
|
||||
expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -224,7 +225,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
flow: PasscodeType.Register,
|
||||
phone: '13000000000',
|
||||
expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(),
|
||||
expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -248,7 +249,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
expect.objectContaining({
|
||||
verification: {
|
||||
userId: 'id',
|
||||
expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(),
|
||||
expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(),
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
},
|
||||
})
|
||||
|
@ -299,7 +300,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
flow: PasscodeType.SignIn,
|
||||
email: 'a@a.com',
|
||||
expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(),
|
||||
expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -322,7 +323,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
flow: PasscodeType.Register,
|
||||
email: 'a@a.com',
|
||||
expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(),
|
||||
expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -346,7 +347,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
expect.objectContaining({
|
||||
verification: {
|
||||
userId: 'id',
|
||||
expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(),
|
||||
expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(),
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
},
|
||||
})
|
||||
|
@ -382,7 +383,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
phone: '13000000000',
|
||||
flow: PasscodeType.SignIn,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -406,7 +407,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
phone: '13000000000',
|
||||
flow: PasscodeType.Register,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -427,7 +428,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
phone: '13000000000',
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -441,7 +442,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
phone: '13000000000',
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -469,7 +470,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
phone: '13000000000',
|
||||
flow: PasscodeType.SignIn,
|
||||
expiresAt: dayjs().subtract(1, 'day').toISOString(),
|
||||
expiresAt: subDays(Date.now(), 1).toISOString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -483,7 +484,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
email: 'XX@foo',
|
||||
flow: PasscodeType.SignIn,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -497,7 +498,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
phone: '13000000001',
|
||||
flow: PasscodeType.SignIn,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -517,7 +518,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
email: 'a@a.com',
|
||||
flow: PasscodeType.SignIn,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -541,7 +542,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
email: 'a@a.com',
|
||||
flow: PasscodeType.Register,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -564,7 +565,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
email: 'a@a.com',
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -578,7 +579,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
email: 'a@a.com',
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -591,7 +592,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
flow: PasscodeType.SignIn,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -605,7 +606,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
email: 'b@a.com',
|
||||
flow: PasscodeType.SignIn,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -625,7 +626,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
phone: '13000000001',
|
||||
flow: PasscodeType.Register,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -647,7 +648,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
phone: '13000000001',
|
||||
flow: PasscodeType.SignIn,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -668,7 +669,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
phone: '13000000001',
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -682,7 +683,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
phone: '13000000001',
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -695,7 +696,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
flow: PasscodeType.Register,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -709,7 +710,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
phone: '13000000000',
|
||||
flow: PasscodeType.Register,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -729,7 +730,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
email: 'b@a.com',
|
||||
flow: PasscodeType.Register,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -751,7 +752,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
email: 'b@a.com',
|
||||
flow: PasscodeType.SignIn,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -772,7 +773,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
email: 'b@a.com',
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -786,7 +787,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
email: 'b@a.com',
|
||||
flow: PasscodeType.ForgotPassword,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -799,7 +800,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
result: {
|
||||
verification: {
|
||||
flow: PasscodeType.Register,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -813,7 +814,7 @@ describe('session -> passwordlessRoutes', () => {
|
|||
verification: {
|
||||
email: 'a@a.com',
|
||||
flow: PasscodeType.Register,
|
||||
expiresAt: dayjs().add(1, 'day').toISOString(),
|
||||
expiresAt: getTomorrowIsoString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { LogType, PasscodeType } from '@logto/schemas';
|
||||
import { logTypeGuard } from '@logto/schemas';
|
||||
import type { Truthy } from '@silverhand/essentials';
|
||||
import dayjs from 'dayjs';
|
||||
import { addSeconds, isAfter, isValid, parseISO } from 'date-fns';
|
||||
import type { Context } from 'koa';
|
||||
import type { Provider } from 'oidc-provider';
|
||||
import type { ZodType } from 'zod';
|
||||
|
@ -60,8 +60,9 @@ export const getVerificationStorageFromInteraction = async <T = VerificationStor
|
|||
};
|
||||
|
||||
export const checkValidateExpiration = (expiresAt: string) => {
|
||||
const parsed = parseISO(expiresAt);
|
||||
assertThat(
|
||||
dayjs(expiresAt).isValid() && dayjs(expiresAt).isAfter(dayjs()),
|
||||
isValid(parsed) && isAfter(parsed, Date.now()),
|
||||
new RequestError({ code: 'session.verification_expired', status: 401 })
|
||||
);
|
||||
};
|
||||
|
@ -75,7 +76,7 @@ export const assignVerificationResult = async (
|
|||
) => {
|
||||
const verification: VerificationStorage = {
|
||||
...verificationData,
|
||||
expiresAt: dayjs().add(verificationTimeout, 'second').toISOString(),
|
||||
expiresAt: addSeconds(Date.now(), verificationTimeout).toISOString(),
|
||||
};
|
||||
|
||||
await provider.interactionResult(ctx.req, ctx.res, {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@jest/types": "^29.1.2",
|
||||
"@logto/js": "1.0.0-beta.11",
|
||||
"@logto/node": "1.0.0-beta.12",
|
||||
"@logto/schemas": "workspace:^",
|
||||
"@peculiar/webcrypto": "^1.3.3",
|
||||
|
|
|
@ -10,7 +10,7 @@ import { extractCookie } from '@/utils';
|
|||
|
||||
import { MemoryStorage } from './storage';
|
||||
|
||||
const defaultConfig = {
|
||||
export const defaultConfig = {
|
||||
endpoint: logtoUrl,
|
||||
appId: demoAppApplicationId,
|
||||
persistAccessToken: false,
|
||||
|
@ -18,8 +18,8 @@ const defaultConfig = {
|
|||
|
||||
export default class MockClient {
|
||||
public interactionCookie?: string;
|
||||
private navigateUrl?: string;
|
||||
|
||||
private navigateUrl?: string;
|
||||
private readonly storage: MemoryStorage;
|
||||
private readonly logto: LogtoClient;
|
||||
|
||||
|
@ -88,6 +88,10 @@ export default class MockClient {
|
|||
return this.logto.getAccessToken(resource);
|
||||
}
|
||||
|
||||
public async getRefreshToken() {
|
||||
return this.logto.getRefreshToken();
|
||||
}
|
||||
|
||||
public async signOut(postSignOutRedirectUri?: string) {
|
||||
return this.logto.signOut(postSignOutRedirectUri);
|
||||
}
|
||||
|
|
4
packages/integration-tests/src/include.d/node-fetch.d.ts
vendored
Normal file
4
packages/integration-tests/src/include.d/node-fetch.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
declare module 'node-fetch' {
|
||||
const nodeFetch: typeof fetch;
|
||||
export = nodeFetch;
|
||||
}
|
|
@ -1,8 +1,13 @@
|
|||
import path from 'path';
|
||||
|
||||
import { fetchTokenByRefreshToken } from '@logto/js';
|
||||
import { managementResource } from '@logto/schemas/lib/seeds';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import { signInWithUsernameAndPassword } from '@/api';
|
||||
import MockClient from '@/client';
|
||||
import MockClient, { defaultConfig } from '@/client';
|
||||
import { logtoUrl } from '@/constants';
|
||||
import { createUserByAdmin } from '@/helpers';
|
||||
import { generateUsername, generatePassword } from '@/utils';
|
||||
|
||||
|
@ -36,4 +41,41 @@ describe('get access token', () => {
|
|||
// Request for invalid resource should throw
|
||||
void expect(client.getAccessToken('api.foo.com')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('sign-in and get multiple Access Token by the same Refresh Token within refreshTokenReuseInterval', async () => {
|
||||
const client = new MockClient({ resources: [managementResource.indicator] });
|
||||
await client.initSession();
|
||||
assert(client.interactionCookie, new Error('Session not found'));
|
||||
|
||||
const { redirectTo } = await signInWithUsernameAndPassword(
|
||||
username,
|
||||
password,
|
||||
client.interactionCookie
|
||||
);
|
||||
|
||||
await client.processSession(redirectTo);
|
||||
assert(client.isAuthenticated, new Error('Sign in get get access token failed'));
|
||||
|
||||
const refreshToken = await client.getRefreshToken();
|
||||
assert(refreshToken, new Error('No Refresh Token found'));
|
||||
|
||||
const getAccessTokenByRefreshToken = async () =>
|
||||
fetchTokenByRefreshToken(
|
||||
{
|
||||
clientId: defaultConfig.appId,
|
||||
tokenEndpoint: path.join(logtoUrl, '/oidc/token'),
|
||||
refreshToken,
|
||||
resource: managementResource.indicator,
|
||||
},
|
||||
async <T>(...args: Parameters<typeof fetch>): Promise<T> => {
|
||||
const response = await fetch(...args);
|
||||
assert(response.ok, new Error('Request error'));
|
||||
|
||||
return response.json();
|
||||
}
|
||||
);
|
||||
|
||||
// Allow to use the same refresh token to fetch access token within short time period
|
||||
await Promise.all([getAccessTokenByRefreshToken(), getAccessTokenByRefreshToken()]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
"dependencies": {
|
||||
"@logto/schemas": "workspace:^",
|
||||
"@silverhand/essentials": "^1.3.0",
|
||||
"dayjs": "^1.10.5",
|
||||
"find-up": "^5.0.0",
|
||||
"nanoid": "^3.3.4",
|
||||
"slonik": "^30.0.0"
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import dayjs from 'dayjs';
|
||||
import { sql } from 'slonik';
|
||||
import { SqlToken } from 'slonik/dist/src/tokens.js';
|
||||
|
||||
|
@ -124,7 +123,7 @@ describe('convertToTimestamp()', () => {
|
|||
});
|
||||
|
||||
it('converts to sql per time parameter', () => {
|
||||
const time = dayjs(123_123_123);
|
||||
const time = new Date(123_123_123);
|
||||
|
||||
expect(convertToTimestamp(time)).toEqual({
|
||||
sql: 'to_timestamp($1)',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type { SchemaValuePrimitive, SchemaValue } from '@logto/schemas';
|
||||
import type { Falsy } from '@silverhand/essentials';
|
||||
import { notFalsy } from '@silverhand/essentials';
|
||||
import dayjs from 'dayjs';
|
||||
import type { SqlSqlToken, SqlToken, QueryResult, IdentifierSqlToken } from 'slonik';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
|
@ -76,7 +75,8 @@ export const convertToIdentifiers = <T extends Table>({ table, fields }: T, with
|
|||
};
|
||||
};
|
||||
|
||||
export const convertToTimestamp = (time = dayjs()) => sql`to_timestamp(${time.valueOf() / 1000})`;
|
||||
export const convertToTimestamp = (time = new Date()) =>
|
||||
sql`to_timestamp(${time.valueOf() / 1000})`;
|
||||
|
||||
export const manyRows = async <T>(query: Promise<QueryResult<T>>): Promise<readonly T[]> => {
|
||||
const { rows } = await query;
|
||||
|
|
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
|
@ -134,7 +134,7 @@ importers:
|
|||
clean-deep: ^3.4.0
|
||||
cross-env: ^7.0.3
|
||||
csstype: ^3.0.11
|
||||
dayjs: ^1.10.5
|
||||
date-fns: ^2.29.3
|
||||
deepmerge: ^4.2.2
|
||||
dnd-core: ^16.0.0
|
||||
eslint: ^8.21.0
|
||||
|
@ -170,6 +170,8 @@ importers:
|
|||
swr: ^1.3.0
|
||||
typescript: ^4.7.4
|
||||
zod: ^3.19.1
|
||||
dependencies:
|
||||
date-fns: 2.29.3
|
||||
devDependencies:
|
||||
'@fontsource/roboto-mono': 4.5.7
|
||||
'@logto/core-kit': 1.0.0-beta.20
|
||||
|
@ -201,7 +203,6 @@ importers:
|
|||
clean-deep: 3.4.0
|
||||
cross-env: 7.0.3
|
||||
csstype: 3.0.11
|
||||
dayjs: 1.10.7
|
||||
deepmerge: 4.2.2
|
||||
dnd-core: 16.0.0
|
||||
eslint: 8.21.0
|
||||
|
@ -271,7 +272,7 @@ importers:
|
|||
chalk: ^4
|
||||
clean-deep: ^3.4.0
|
||||
copyfiles: ^2.4.1
|
||||
dayjs: ^1.10.5
|
||||
date-fns: ^2.29.3
|
||||
debug: ^4.3.4
|
||||
decamelize: ^5.0.0
|
||||
deepmerge: ^4.2.2
|
||||
|
@ -329,7 +330,7 @@ importers:
|
|||
'@silverhand/essentials': 1.3.0
|
||||
chalk: 4.1.2
|
||||
clean-deep: 3.4.0
|
||||
dayjs: 1.10.7
|
||||
date-fns: 2.29.3
|
||||
debug: 4.3.4
|
||||
decamelize: 5.0.1
|
||||
deepmerge: 4.2.2
|
||||
|
@ -462,6 +463,7 @@ importers:
|
|||
packages/integration-tests:
|
||||
specifiers:
|
||||
'@jest/types': ^29.1.2
|
||||
'@logto/js': 1.0.0-beta.11
|
||||
'@logto/node': 1.0.0-beta.12
|
||||
'@logto/schemas': workspace:^
|
||||
'@peculiar/webcrypto': ^1.3.3
|
||||
|
@ -487,6 +489,7 @@ importers:
|
|||
typescript: ^4.7.4
|
||||
devDependencies:
|
||||
'@jest/types': 29.1.2
|
||||
'@logto/js': 1.0.0-beta.11
|
||||
'@logto/node': 1.0.0-beta.12
|
||||
'@logto/schemas': link:../schemas
|
||||
'@peculiar/webcrypto': 1.3.3
|
||||
|
@ -623,7 +626,6 @@ importers:
|
|||
'@silverhand/ts-config': 1.2.1
|
||||
'@types/jest': ^29.1.2
|
||||
'@types/node': ^16.0.0
|
||||
dayjs: ^1.10.5
|
||||
eslint: ^8.21.0
|
||||
find-up: ^5.0.0
|
||||
jest: ^29.1.2
|
||||
|
@ -635,7 +637,6 @@ importers:
|
|||
dependencies:
|
||||
'@logto/schemas': link:../schemas
|
||||
'@silverhand/essentials': 1.3.0
|
||||
dayjs: 1.10.7
|
||||
find-up: 5.0.0
|
||||
nanoid: 3.3.4
|
||||
slonik: 30.1.2
|
||||
|
@ -3467,7 +3468,7 @@ packages:
|
|||
'@jest/types': 29.1.2
|
||||
deepmerge: 4.2.2
|
||||
identity-obj-proxy: 3.0.0
|
||||
jest: 29.1.2_k5ytkvaprncdyzidqqws5bqksq
|
||||
jest: 29.1.2_@types+node@16.11.12
|
||||
jest-matcher-specific-error: 1.0.0
|
||||
jest-transform-stub: 2.0.0
|
||||
ts-jest: 29.0.3_37jxomqt5oevoqzq6g3r6n3ili
|
||||
|
@ -5648,8 +5649,10 @@ packages:
|
|||
whatwg-url: 11.0.0
|
||||
dev: true
|
||||
|
||||
/dayjs/1.10.7:
|
||||
resolution: {integrity: sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==}
|
||||
/date-fns/2.29.3:
|
||||
resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
|
||||
engines: {node: '>=0.11'}
|
||||
dev: false
|
||||
|
||||
/debug/2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
|
@ -13451,7 +13454,7 @@ packages:
|
|||
'@jest/types': 29.1.2
|
||||
bs-logger: 0.2.6
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
jest: 29.1.2_k5ytkvaprncdyzidqqws5bqksq
|
||||
jest: 29.1.2_@types+node@16.11.12
|
||||
jest-util: 29.2.1
|
||||
json5: 2.2.1
|
||||
lodash.memoize: 4.1.2
|
||||
|
|
Loading…
Add table
Reference in a new issue