mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Feat/use collaborate data (#2067)
* feat: initial architecture * feat: add generic types * fix: refactor * feat: style notification * feat: remove useFeatureImmutable * fix: remove casting * fix: ensure data is present * fix: revive useFeatureImmutable * Update frontend/src/component/common/StaleDataNotification/StaleDataNotification.tsx Co-authored-by: Nuno Góis <github@nunogois.com> * Update frontend/src/component/common/StaleDataNotification/StaleDataNotification.tsx Co-authored-by: Nuno Góis <github@nunogois.com> * Update frontend/src/component/common/StaleDataNotification/StaleDataNotification.tsx Co-authored-by: Nuno Góis <github@nunogois.com> * Update frontend/src/component/common/StaleDataNotification/StaleDataNotification.tsx Co-authored-by: Nuno Góis <github@nunogois.com> * Update frontend/src/component/common/StaleDataNotification/StaleDataNotification.tsx Co-authored-by: Nuno Góis <github@nunogois.com> * fix: pr comments * fix: change order Co-authored-by: Nuno Góis <github@nunogois.com>
This commit is contained in:
parent
1cf42d6527
commit
54633500fd
@ -127,5 +127,8 @@
|
|||||||
"ignorePatterns": [
|
"ignorePatterns": [
|
||||||
"cypress"
|
"cypress"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dequal": "^2.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ interface IAnimateOnMountProps {
|
|||||||
mounted: boolean;
|
mounted: boolean;
|
||||||
enter: string;
|
enter: string;
|
||||||
start: string;
|
start: string;
|
||||||
leave: string;
|
leave?: string;
|
||||||
container?: string;
|
container?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
onStart?: () => void;
|
onStart?: () => void;
|
||||||
@ -39,7 +39,7 @@ const AnimateOnMount: FC<IAnimateOnMountProps> = ({
|
|||||||
if (!leave) {
|
if (!leave) {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
}
|
}
|
||||||
setStyles(leave);
|
setStyles(leave || '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [mounted, enter, onStart, leave]);
|
}, [mounted, enter, onStart, leave]);
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
import { Typography, Button, useTheme, useMediaQuery } from '@mui/material';
|
||||||
|
import EventDiff from 'component/events/EventDiff/EventDiff';
|
||||||
|
import { useThemeStyles } from 'themes/themeStyles';
|
||||||
|
import AnimateOnMount from 'component/common/AnimateOnMount/AnimateOnMount';
|
||||||
|
|
||||||
|
interface IStaleDataNotification {
|
||||||
|
refresh: () => void;
|
||||||
|
afterSubmitAction: () => void;
|
||||||
|
data: unknown;
|
||||||
|
cache: unknown;
|
||||||
|
show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StaleDataNotification = ({
|
||||||
|
refresh,
|
||||||
|
show,
|
||||||
|
afterSubmitAction,
|
||||||
|
data,
|
||||||
|
cache,
|
||||||
|
}: IStaleDataNotification) => {
|
||||||
|
const { classes: themeStyles } = useThemeStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
|
const getStyles = () => {
|
||||||
|
const base = {
|
||||||
|
padding: `${theme.spacing(3)} ${theme.spacing(4)}`,
|
||||||
|
boxShadow: theme.boxShadows.elevated,
|
||||||
|
borderRadius: theme.shape.borderRadiusLarge,
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
maxWidth: theme.spacing(75),
|
||||||
|
zIndex: theme.zIndex.mobileStepper,
|
||||||
|
};
|
||||||
|
if (isExtraSmallScreen) {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
right: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
borderRadius: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimateOnMount
|
||||||
|
mounted={show}
|
||||||
|
start={themeStyles.fadeInBottomStartWithoutFixed}
|
||||||
|
enter={themeStyles.fadeInBottomEnter}
|
||||||
|
style={getStyles()}
|
||||||
|
>
|
||||||
|
<Typography variant="h5" sx={{ my: 2, mb: 2 }}>
|
||||||
|
Your data is stale
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" sx={{ my: 2, mb: 3 }}>
|
||||||
|
The data you have been working on is stale, would you like to
|
||||||
|
refresh your data? This may happen if someone has been making
|
||||||
|
changes to the data while you were working.
|
||||||
|
</Typography>
|
||||||
|
<EventDiff entry={{ preData: cache, data }} />
|
||||||
|
<Button
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
refresh();
|
||||||
|
afterSubmitAction();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Refresh data
|
||||||
|
</Button>
|
||||||
|
</AnimateOnMount>
|
||||||
|
);
|
||||||
|
};
|
@ -11,7 +11,7 @@ const DIFF_PREFIXES: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface IEventDiffProps {
|
interface IEventDiffProps {
|
||||||
entry: IEvent;
|
entry: Partial<IEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventDiff = ({ entry }: IEventDiffProps) => {
|
const EventDiff = ({ entry }: IEventDiffProps) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { useRequiredQueryParam } from 'hooks/useRequiredQueryParam';
|
import { useRequiredQueryParam } from 'hooks/useRequiredQueryParam';
|
||||||
import { FeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm';
|
import { FeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm';
|
||||||
@ -24,6 +24,9 @@ import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmu
|
|||||||
import { useFormErrors } from 'hooks/useFormErrors';
|
import { useFormErrors } from 'hooks/useFormErrors';
|
||||||
import { createFeatureStrategy } from 'utils/createFeatureStrategy';
|
import { createFeatureStrategy } from 'utils/createFeatureStrategy';
|
||||||
import { useStrategy } from 'hooks/api/getters/useStrategy/useStrategy';
|
import { useStrategy } from 'hooks/api/getters/useStrategy/useStrategy';
|
||||||
|
import { useCollaborateData } from 'hooks/useCollaborateData';
|
||||||
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
|
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||||
|
|
||||||
export const FeatureStrategyCreate = () => {
|
export const FeatureStrategyCreate = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
@ -42,10 +45,30 @@ export const FeatureStrategyCreate = () => {
|
|||||||
const { unleashUrl } = uiConfig;
|
const { unleashUrl } = uiConfig;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { feature, refetchFeature } = useFeatureImmutable(
|
const { feature, refetchFeature } = useFeature(projectId, featureId);
|
||||||
projectId,
|
const ref = useRef<IFeatureToggle>(feature);
|
||||||
featureId
|
|
||||||
);
|
const { data, staleDataNotification, forceRefreshCache } =
|
||||||
|
useCollaborateData<IFeatureToggle>(
|
||||||
|
{
|
||||||
|
unleashGetter: useFeature,
|
||||||
|
params: [projectId, featureId],
|
||||||
|
dataKey: 'feature',
|
||||||
|
refetchFunctionKey: 'refetchFeature',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
feature,
|
||||||
|
{
|
||||||
|
afterSubmitAction: refetchFeature,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current.name === '' && feature.name) {
|
||||||
|
forceRefreshCache(feature);
|
||||||
|
ref.current = feature;
|
||||||
|
}
|
||||||
|
}, [feature]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (strategyDefinition) {
|
if (strategyDefinition) {
|
||||||
@ -81,6 +104,8 @@ export const FeatureStrategyCreate = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
modal
|
modal
|
||||||
@ -99,7 +124,7 @@ export const FeatureStrategyCreate = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FeatureStrategyForm
|
<FeatureStrategyForm
|
||||||
feature={feature}
|
feature={data}
|
||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
setStrategy={setStrategy}
|
setStrategy={setStrategy}
|
||||||
segments={segments}
|
segments={segments}
|
||||||
@ -110,6 +135,7 @@ export const FeatureStrategyCreate = () => {
|
|||||||
permission={CREATE_FEATURE_STRATEGY}
|
permission={CREATE_FEATURE_STRATEGY}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
|
{staleDataNotification}
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { FeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm';
|
import { FeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm';
|
||||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
@ -18,10 +18,12 @@ import { ISegment } from 'interfaces/segment';
|
|||||||
import { useSegmentsApi } from 'hooks/api/actions/useSegmentsApi/useSegmentsApi';
|
import { useSegmentsApi } from 'hooks/api/actions/useSegmentsApi/useSegmentsApi';
|
||||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||||
import { formatStrategyName } from 'utils/strategyNames';
|
import { formatStrategyName } from 'utils/strategyNames';
|
||||||
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
|
|
||||||
import { useFormErrors } from 'hooks/useFormErrors';
|
import { useFormErrors } from 'hooks/useFormErrors';
|
||||||
import { useStrategy } from 'hooks/api/getters/useStrategy/useStrategy';
|
import { useStrategy } from 'hooks/api/getters/useStrategy/useStrategy';
|
||||||
import { sortStrategyParameters } from 'utils/sortStrategyParameters';
|
import { sortStrategyParameters } from 'utils/sortStrategyParameters';
|
||||||
|
import { useCollaborateData } from 'hooks/useCollaborateData';
|
||||||
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
|
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||||
|
|
||||||
export const FeatureStrategyEdit = () => {
|
export const FeatureStrategyEdit = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
@ -40,10 +42,31 @@ export const FeatureStrategyEdit = () => {
|
|||||||
const { unleashUrl } = uiConfig;
|
const { unleashUrl } = uiConfig;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { feature, refetchFeature } = useFeatureImmutable(
|
const { feature, refetchFeature } = useFeature(projectId, featureId);
|
||||||
projectId,
|
|
||||||
featureId
|
const ref = useRef<IFeatureToggle>(feature);
|
||||||
);
|
|
||||||
|
const { data, staleDataNotification, forceRefreshCache } =
|
||||||
|
useCollaborateData<IFeatureToggle>(
|
||||||
|
{
|
||||||
|
unleashGetter: useFeature,
|
||||||
|
params: [projectId, featureId],
|
||||||
|
dataKey: 'feature',
|
||||||
|
refetchFunctionKey: 'refetchFeature',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
feature,
|
||||||
|
{
|
||||||
|
afterSubmitAction: refetchFeature,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current.name === '' && feature.name) {
|
||||||
|
forceRefreshCache(feature);
|
||||||
|
ref.current = feature;
|
||||||
|
}
|
||||||
|
}, [feature]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
segments: savedStrategySegments,
|
segments: savedStrategySegments,
|
||||||
@ -51,11 +74,11 @@ export const FeatureStrategyEdit = () => {
|
|||||||
} = useSegments(strategyId);
|
} = useSegments(strategyId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedStrategy = feature.environments
|
const savedStrategy = data?.environments
|
||||||
.flatMap(environment => environment.strategies)
|
.flatMap(environment => environment.strategies)
|
||||||
.find(strategy => strategy.id === strategyId);
|
.find(strategy => strategy.id === strategyId);
|
||||||
setStrategy(prev => ({ ...prev, ...savedStrategy }));
|
setStrategy(prev => ({ ...prev, ...savedStrategy }));
|
||||||
}, [strategyId, feature]);
|
}, [strategyId, data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fill in the selected segments once they've been fetched.
|
// Fill in the selected segments once they've been fetched.
|
||||||
@ -96,6 +119,8 @@ export const FeatureStrategyEdit = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
modal
|
modal
|
||||||
@ -115,7 +140,7 @@ export const FeatureStrategyEdit = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FeatureStrategyForm
|
<FeatureStrategyForm
|
||||||
feature={feature}
|
feature={data}
|
||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
setStrategy={setStrategy}
|
setStrategy={setStrategy}
|
||||||
segments={segments}
|
segments={segments}
|
||||||
@ -126,6 +151,7 @@ export const FeatureStrategyEdit = () => {
|
|||||||
permission={UPDATE_FEATURE_STRATEGY}
|
permission={UPDATE_FEATURE_STRATEGY}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
|
{staleDataNotification}
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
84
frontend/src/hooks/useCollaborateData.tsx
Normal file
84
frontend/src/hooks/useCollaborateData.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { SWRConfiguration } from 'swr';
|
||||||
|
import { dequal } from 'dequal';
|
||||||
|
import { StaleDataNotification } from 'component/common/StaleDataNotification/StaleDataNotification';
|
||||||
|
|
||||||
|
interface IFormatUnleashGetterOutput<Type> {
|
||||||
|
data: Type;
|
||||||
|
refetch: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatUnleashGetter = <Type,>({
|
||||||
|
unleashGetter,
|
||||||
|
dataKey = '',
|
||||||
|
refetchFunctionKey = '',
|
||||||
|
options = {},
|
||||||
|
params = [''],
|
||||||
|
}: IGetterOptions): IFormatUnleashGetterOutput<Type> => {
|
||||||
|
const result = unleashGetter(...params, { refreshInterval: 5, ...options });
|
||||||
|
|
||||||
|
return { data: result[dataKey], refetch: result[refetchFunctionKey] };
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IGetterOptions {
|
||||||
|
dataKey: string;
|
||||||
|
unleashGetter: any;
|
||||||
|
options: SWRConfiguration;
|
||||||
|
refetchFunctionKey: string;
|
||||||
|
params: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICollaborateDataOutput<Type> {
|
||||||
|
staleDataNotification: JSX.Element;
|
||||||
|
data: Type | null;
|
||||||
|
refetch: () => void;
|
||||||
|
forceRefreshCache: (data: Type) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IStaleNotificationOptions {
|
||||||
|
afterSubmitAction: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCollaborateData = <Type,>(
|
||||||
|
getterOptions: IGetterOptions,
|
||||||
|
initialData: Type,
|
||||||
|
notificationOptions: IStaleNotificationOptions
|
||||||
|
): ICollaborateDataOutput<Type> => {
|
||||||
|
const { data, refetch } = formatUnleashGetter<Type>(getterOptions);
|
||||||
|
const [cache, setCache] = useState<Type | null>(initialData || null);
|
||||||
|
const [dataModified, setDataModified] = useState(false);
|
||||||
|
|
||||||
|
const forceRefreshCache = (data: Type) => {
|
||||||
|
setDataModified(false);
|
||||||
|
setCache(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cache === null) {
|
||||||
|
setCache(initialData);
|
||||||
|
}
|
||||||
|
}, [initialData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const equal = dequal(data, cache);
|
||||||
|
|
||||||
|
if (!equal) {
|
||||||
|
setDataModified(true);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: cache,
|
||||||
|
refetch,
|
||||||
|
staleDataNotification: (
|
||||||
|
<StaleDataNotification
|
||||||
|
cache={cache}
|
||||||
|
data={data}
|
||||||
|
refresh={() => forceRefreshCache(data)}
|
||||||
|
show={dataModified}
|
||||||
|
afterSubmitAction={notificationOptions.afterSubmitAction}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
forceRefreshCache,
|
||||||
|
};
|
||||||
|
};
|
@ -3603,6 +3603,11 @@ delayed-stream@~1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||||
|
|
||||||
|
dequal@^2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
|
||||||
|
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
||||||
|
|
||||||
diff-sequences@^27.5.1:
|
diff-sequences@^27.5.1:
|
||||||
version "27.5.1"
|
version "27.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
|
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
|
||||||
|
@ -130,11 +130,14 @@
|
|||||||
"ts-toolbelt": "^9.6.0",
|
"ts-toolbelt": "^9.6.0",
|
||||||
"type-is": "^1.6.18",
|
"type-is": "^1.6.18",
|
||||||
"unleash-client": "3.15.0",
|
"unleash-client": "3.15.0",
|
||||||
|
"use-deep-compare-effect": "^1.8.1",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@apidevtools/swagger-parser": "10.1.0",
|
"@apidevtools/swagger-parser": "10.1.0",
|
||||||
"@babel/core": "7.18.13",
|
"@babel/core": "7.18.13",
|
||||||
|
"@swc/core": "1.2.246",
|
||||||
|
"@swc/jest": "0.2.22",
|
||||||
"@types/bcryptjs": "2.4.2",
|
"@types/bcryptjs": "2.4.2",
|
||||||
"@types/cors": "2.8.12",
|
"@types/cors": "2.8.12",
|
||||||
"@types/express": "4.17.13",
|
"@types/express": "4.17.13",
|
||||||
@ -176,8 +179,6 @@
|
|||||||
"source-map-support": "0.5.21",
|
"source-map-support": "0.5.21",
|
||||||
"superagent": "8.0.0",
|
"superagent": "8.0.0",
|
||||||
"supertest": "6.2.4",
|
"supertest": "6.2.4",
|
||||||
"@swc/core": "1.2.246",
|
|
||||||
"@swc/jest": "0.2.22",
|
|
||||||
"ts-node": "10.9.1",
|
"ts-node": "10.9.1",
|
||||||
"tsc-watch": "5.0.3",
|
"tsc-watch": "5.0.3",
|
||||||
"typescript": "4.8.2"
|
"typescript": "4.8.2"
|
||||||
|
20
yarn.lock
20
yarn.lock
@ -522,6 +522,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.12.5":
|
||||||
|
version "7.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259"
|
||||||
|
integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/template@^7.16.7":
|
"@babel/template@^7.16.7":
|
||||||
version "7.16.7"
|
version "7.16.7"
|
||||||
resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz"
|
resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz"
|
||||||
@ -2686,6 +2693,11 @@ depd@~2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
|
||||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||||
|
|
||||||
|
dequal@^2.0.2:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
|
||||||
|
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
||||||
|
|
||||||
destroy@~1.0.4:
|
destroy@~1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz"
|
resolved "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz"
|
||||||
@ -7100,6 +7112,14 @@ uri-js@^4.2.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.0"
|
punycode "^2.1.0"
|
||||||
|
|
||||||
|
use-deep-compare-effect@^1.8.1:
|
||||||
|
version "1.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-deep-compare-effect/-/use-deep-compare-effect-1.8.1.tgz#ef0ce3b3271edb801da1ec23bf0754ef4189d0c6"
|
||||||
|
integrity sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.5"
|
||||||
|
dequal "^2.0.2"
|
||||||
|
|
||||||
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user