Implement a mechanism to overload kcContext

This commit is contained in:
garronej
2021-06-28 04:04:48 +02:00
parent e160882db9
commit eac28f97b8
25 changed files with 604 additions and 48 deletions

View File

@ -12,7 +12,7 @@
"clean": "rimraf dist/",
"build": "yarn clean && tsc && yarn grant-exec-perms && yarn copy-files",
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
"test": "node dist/test",
"test": "node dist/test/bin && node dist/test/lib",
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/",
"generate-messages": "node dist/bin/generate-i18n-messages.js"
},
@ -46,16 +46,18 @@
"properties-parser": "^0.3.1",
"react": "^17.0.1",
"rimraf": "^3.0.2",
"typescript": "^4.2.3"
"typescript": "^4.2.3",
"ts-toolbelt": "^9.6.0"
},
"dependencies": {
"cheerio": "^1.0.0-rc.5",
"evt": "2.0.0-beta.15",
"evt": "2.0.0-beta.20",
"minimal-polyfills": "^2.1.6",
"path": "^0.12.7",
"powerhooks": "^0.1.0",
"powerhooks": "^0.1.6",
"react-markdown": "^5.0.3",
"scripting-tools": "^0.19.13",
"tss-react": "^0.0.12"
"tss-react": "^0.0.12",
"tsafe": "^0.4.1"
}
}

View File

@ -8,7 +8,7 @@ import {
} from "../replaceImportFromStatic";
import fs from "fs";
import { join as pathJoin } from "path";
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
import { objectKeys } from "tsafe/objectKeys";
import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
export const pageIds = [

View File

@ -3,7 +3,7 @@
import * as fs from "fs";
import * as path from "path";
import { crawl } from "./crawl";
import { id } from "evt/tools/typeSafety/id";
import { id } from "tsafe/id";
type TransformSourceCode =
(params: {

View File

@ -1,8 +1,6 @@
import { memo } from "react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage";
@ -10,8 +8,6 @@ export const Error = memo(({ kcContext, ...props }: { kcContext: KcContextBase.E
const { msg } = useKcMessage();
assert(kcContext.message !== undefined);
const { message, client } = kcContext;
return (

View File

@ -1,6 +1,6 @@
import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined";
import { doExtends } from "evt/tools/typeSafety/doExtends";
import { doExtends } from "tsafe/doExtends";
/** Class names can be provided as an array or separated by whitespace */
export type KcPropsGeneric<CssClasses extends string> = { [key in CssClasses]: readonly string[] | string | undefined; };

View File

@ -1,7 +1,7 @@
import type { PageId } from "../../bin/build-keycloak-theme/generateFtl";
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
import { doExtends } from "evt/tools/typeSafety/doExtends";
import { doExtends } from "tsafe/doExtends";
import type { MessageKey } from "../i18n/useKcMessage";
import type { LanguageLabel } from "../i18n/KcLanguageTag";
@ -150,7 +150,8 @@ export declare namespace KcContextBase {
pageId: "error.ftl";
client?: {
baseUrl?: string;
}
},
message: NonNullable<Common["message"]>;
};
export type LoginResetPassword = Common & {

View File

@ -1,28 +1,93 @@
import type { KcContextBase } from "./KcContextBase";
import type { KcContextBase } from "./KcContextBase";
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
import { ftlValuesGlobalName } from "../../bin/build-keycloak-theme/ftlValuesGlobalName";
import type { AndByDiscriminatingKey } from "../tools/AndByDiscriminatingKey";
import type { DeepPartial } from "../tools/DeepPartial";
import { deepAssign } from "../tools/deepAssign";
export function getKcContext<KcContextExtended extends { pageId: string; } = never>(
export type ExtendsKcContextBase<
KcContextExtended extends ({ pageId: string; } | undefined)
> =
KcContextExtended extends undefined ?
KcContextBase :
AndByDiscriminatingKey<
"pageId",
KcContextExtended & KcContextBase.Common,
KcContextBase
>;
export function getKcContext<KcContextExtended extends ({ pageId: string; } | undefined) = undefined>(
params?: {
mockPageId?: KcContextBase["pageId"] | KcContextExtended["pageId"];
kcContextExtendedMock?: KcContextExtended[];
mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"];
mockData?: readonly DeepPartial<ExtendsKcContextBase<KcContextExtended>>[];
}
): { kcContext: (KcContextBase | KcContextExtended & KcContextBase.Common) | undefined; } {
): { kcContext: ExtendsKcContextBase<KcContextExtended> | undefined; } {
const { mockPageId, kcContextExtendedMock } = params ?? { "mockPageId": false };
const {
mockPageId,
mockData
} = params ?? {};
if (mockPageId !== undefined) {
return {
"pageId": mockPageId,
...(kcContextMocks.find(({ pageId }) => pageId === mockPageId) ?? kcContextCommonMock),
...(kcContextExtendedMock?.find(({ pageId }) => pageId === mockPageId) ?? {})
} as any;
//TODO maybe trow if no mock fo custom page
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId);
const partialKcContextCustomMock = mockData?.find(({ pageId }) => pageId === mockPageId);
if (
kcContextDefaultMock === undefined &&
partialKcContextCustomMock === undefined
) {
console.warn([
`WARNING: You declared the non build in page ${mockPageId} but you didn't `,
`provide mock data needed to debug the page outside of Keycloak as you are trying to do now.`,
`Please check the documentation of the getKcContext function`
].join("\n"));
}
const kcContext: any = { "pageId": mockPageId };
deepAssign({
"target": kcContext,
"source": kcContextCommonMock
});
if (kcContextDefaultMock !== undefined) {
deepAssign({
"target": kcContext,
"source": kcContextDefaultMock
});
}
if (partialKcContextCustomMock !== undefined) {
deepAssign({
"target": kcContext,
"source": partialKcContextCustomMock
});
}
return { kcContext };
}
return (window as any)[ftlValuesGlobalName];
return {
"kcContext":
typeof window === "undefined" ?
undefined :
(window as any)[ftlValuesGlobalName]
};
}

View File

@ -5,12 +5,15 @@ import { getKcLanguageTagLabel } from "../../i18n/KcLanguageTag";
//NOTE: Aside because we want to be able to import them from node
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
import { id } from "tsafe/id";
import { join as pathJoin } from "path";
const PUBLIC_URL = process.env["PUBLIC_URL"] ?? "/";
export const kcContextCommonMock: KcContextBase.Common = {
"url": {
"loginAction": "#",
"resourcesPath": `${process.env["PUBLIC_URL"]}/${resourcesPath}`,
"resourcesCommonPath": `${process.env["PUBLIC_URL"]}/${resourcesCommonPath}`,
"resourcesPath": pathJoin(PUBLIC_URL, resourcesPath),
"resourcesCommonPath": pathJoin(PUBLIC_URL, resourcesCommonPath),
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
},
@ -95,7 +98,8 @@ export const kcContextCommonMock: KcContextBase.Common = {
"languageTag": "tr"
}
],
"current": null as any
//"current": null as any
"current": "English"
},
"auth": {
"showUsername": false,
@ -110,6 +114,7 @@ export const kcContextCommonMock: KcContextBase.Common = {
"isAppInitiatedAction": false,
};
Object.defineProperty(
kcContextCommonMock.locale!,
"current",
@ -188,6 +193,10 @@ export const kcContextMocks: KcContextBase[] = [
"pageId": "error.ftl",
"client": {
"baseUrl": "#"
},
"message": {
"type": "error",
"summary": "This is the error message"
}
}),
id<KcContextBase.LoginResetPassword>({

View File

@ -0,0 +1,43 @@
import { KcContextBase } from "./KcContextBase";
import type { AndByDiscriminatingKey } from "../tools/AndByDiscriminatingKey";
export type ExtendsKcContextBase<KcContextExtended extends { pageId: string; }>=
AndByDiscriminatingKey<
"pageId",
KcContextExtended & KcContextBase.Common,
KcContextBase
>;
type KcContextExtended =
{ pageId: "register.ftl"; authorizedMailDomains: string[]; } |
{ pageId: "my-extra-page-1.ftl"; } |
{ pageId: "my-extra-page-2.ftl"; someCustomValue: string; };
const y: ExtendsKcContextBase<KcContextExtended> = null as any;
if (y.pageId === "register.ftl") {
y.authorizedMailDomains;
y.realm.displayName;
y.register
}
if (y.pageId === "my-extra-page-1.ftl") {
y.realm.displayName;
}
if (y.pageId === "my-extra-page-2.ftl") {
y.realm
y.someCustomValue
}

View File

@ -1,5 +1,5 @@
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
import { objectKeys } from "tsafe/objectKeys";
import { kcMessages } from "./kcMessages/login";
export type KcLanguageTag = keyof typeof kcMessages;

View File

@ -1,7 +1,7 @@
import { kcMessages } from "../generated_kcMessages/login";
import { Evt } from "evt";
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
import { objectKeys } from "tsafe/objectKeys";
export const evtTermsUpdated = Evt.asNonPostable(Evt.create<void>());

View File

@ -2,22 +2,39 @@
import { createUseGlobalState } from "powerhooks";
import { getKcContext } from "../getKcContext";
import { getBestMatchAmongKcLanguageTag } from "./KcLanguageTag";
import type { StatefulEvt } from "powerhooks";
import { KcLanguageTag } from "./KcLanguageTag";
const { kcContext } = getKcContext();
//export const { useKcLanguageTag, evtKcLanguageTag } = createUseGlobalState(
const wrap = createUseGlobalState(
"kcLanguageTag",
() => getBestMatchAmongKcLanguageTag(
kcContext?.locale?.current ??
navigator.language
),
() => {
const { kcContext } = getKcContext();
const languageLike =
kcContext?.locale?.current ??
(
typeof navigator === "undefined" ?
undefined :
navigator.language
);
if (languageLike === undefined) {
return "en";
}
return getBestMatchAmongKcLanguageTag(languageLike);
},
{ "persistance": "localStorage" }
);
export const { useKcLanguageTag } = wrap;
export function getEvtKcLanguage() {
export function getEvtKcLanguage(): StatefulEvt<KcLanguageTag> {
return wrap.evtKcLanguageTag;
}

View File

@ -0,0 +1,35 @@
export type AndByDiscriminatingKey<
DiscriminatingKey extends string,
U1 extends Record<DiscriminatingKey, string>,
U2 extends Record<DiscriminatingKey, string>
> =
AndByDiscriminatingKey.Tf1<DiscriminatingKey, U1, U1, U2>;
export declare namespace AndByDiscriminatingKey {
export type Tf1<
DiscriminatingKey extends string,
U1,
U1Again extends Record<DiscriminatingKey, string>,
U2 extends Record<DiscriminatingKey, string>
> =
U1 extends Pick<U2, DiscriminatingKey> ?
Tf2<DiscriminatingKey, U1, U2, U1Again> :
U1;
export type Tf2<
DiscriminatingKey extends string,
SingletonU1 extends Record<DiscriminatingKey, string>,
U2,
U1 extends Record<DiscriminatingKey, string>
> =
U2 extends Pick<SingletonU1, DiscriminatingKey> ?
U2 & SingletonU1 :
U2 extends Pick<U1, DiscriminatingKey> ?
never :
U2;
}

4
src/lib/tools/DeepPartial.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};

View File

@ -1,2 +1,2 @@
export { assert } from "evt/tools/typeSafety/assert";
export { assert } from "tsafe/assert";

View File

@ -0,0 +1,56 @@
import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
export function deepAssign(
params: {
target: Record<string, unknown>;
source: Record<string, unknown>;
}
) {
const { target, source } = params;
Object.keys(source).forEach(key => {
var dereferencedSource = source[key];
if (
target[key] === undefined ||
!(dereferencedSource instanceof Object)
) {
Object.defineProperty(
target,
key,
{
"enumerable": true,
"value": dereferencedSource
}
);
return;
}
const dereferencedTarget = target[key];
if (dereferencedSource instanceof Array) {
assert(is<unknown[]>(dereferencedTarget));
assert(is<unknown[]>(dereferencedSource));
dereferencedSource.forEach(entry => dereferencedTarget.push(entry));
return;
}
assert(is<Record<string, unknown>>(dereferencedTarget));
assert(is<Record<string, unknown>>(dereferencedSource));
deepAssign({
"target": dereferencedTarget,
"source": dereferencedSource
});
});
}

View File

@ -0,0 +1,4 @@
export function deepClone<T>(arg: T): T {
return JSON.parse(JSON.stringify(arg));
}

View File

@ -1,6 +1,6 @@
import { join as pathJoin } from "path";
import { generateKeycloakThemeResources } from "../bin/build-keycloak-theme/generateKeycloakThemeResources";
import { generateKeycloakThemeResources } from "../../bin/build-keycloak-theme/generateKeycloakThemeResources";
import {
setupSampleReactProject,
sampleReactProjectDirPath

View File

@ -6,7 +6,7 @@ import {
} from "./setupSampleReactProject";
import * as st from "scripting-tools";
import { join as pathJoin } from "path";
import { getProjectRoot } from "../bin/tools/getProjectRoot";
import { getProjectRoot } from "../../bin/tools/getProjectRoot";
setupSampleReactProject();

View File

@ -3,7 +3,7 @@ import { 
replaceImportsFromStaticInJsCode,
replaceImportsInCssCode,
generateCssCodeToDefineGlobals
} from "../bin/build-keycloak-theme/replaceImportFromStatic";
} from "../../bin/build-keycloak-theme/replaceImportFromStatic";
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": `

View File

@ -1,7 +1,7 @@
import { getProjectRoot } from "../bin/tools/getProjectRoot";
import { getProjectRoot } from "../../bin/tools/getProjectRoot";
import { join as pathJoin } from "path";
import { downloadAndUnzip } from "../bin/tools/downloadAndUnzip";
import { downloadAndUnzip } from "../../bin/tools/downloadAndUnzip";
export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project");

View File

@ -0,0 +1,207 @@
import { getKcContext } from "../../lib/getKcContext";
import type { KcContextBase } from "../../lib/getKcContext";
import type { ExtendsKcContextBase } from "../../lib/getKcContext/getKcContext";
import { same } from "evt/tools/inDepth";
import { doExtends } from "tsafe/doExtends";
import { assert } from "tsafe/assert";
import { kcContextMocks, kcContextCommonMock } from "../../lib/getKcContext/kcContextMocks";
import { deepClone } from "../../lib/tools/deepClone";
import type { Any } from "ts-toolbelt";
const authorizedMailDomains = [
"example.com",
"another-example.com",
"*.yet-another-example.com",
"*.example.com",
"hello-world.com"
];
const displayName = "this is an overwritten common value";
const aNonStandardValue = "a non standard value";
type KcContextExtended = {
pageId: "register.ftl";
authorizedMailDomains: string[];
} | {
pageId: "my-extra-page-1.ftl";
} | {
pageId: "my-extra-page-2.ftl";
aNonStandardValue: string;
};
function getKcContextProxy(
params: {
mockPageId: ExtendsKcContextBase<KcContextExtended>["pageId"];
}
) {
const { mockPageId } = params;
const { kcContext } = getKcContext<KcContextExtended>({
mockPageId,
"mockData": [
{
"pageId": "login.ftl",
"realm": { displayName }
},
{
"pageId": "register.ftl",
authorizedMailDomains
},
{
"pageId": "my-extra-page-2.ftl",
aNonStandardValue
}
]
});
return { kcContext };
}
{
const pageId= "login.ftl";
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
//@ts-expect-error
doExtends<Any.Equals<typeof kcContext, any>, 1>();
assert(kcContext?.pageId === pageId);
doExtends<typeof kcContext, KcContextBase.Login>();
assert(same(
//NOTE: deepClone for printIfExists or other functions...
deepClone(kcContext),
(() => {
const mock = deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!);
mock.realm.displayName = displayName;
return mock;
})()
));
console.log(`PASS ${pageId}`);
}
{
const pageId = "register.ftl";
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
//@ts-expect-error
doExtends<Any.Equals<typeof kcContext, any>, 1>();
assert(kcContext?.pageId === pageId);
doExtends<typeof kcContext, KcContextBase.Register>();
assert(same(
deepClone(kcContext),
(() => {
const mock = deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!);
Object.assign(mock, { authorizedMailDomains });
return mock;
})()
));
console.log(`PASS ${pageId}`);
}
{
const pageId = "my-extra-page-2.ftl";
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
//@ts-expect-error
doExtends<Any.Equals<typeof kcContext, any>, 1>();
assert(kcContext?.pageId === pageId);
//@ts-expect-error
doExtends<typeof kcContext, KcContextBase>();
doExtends<typeof kcContext, KcContextBase.Common>();
assert(same(
deepClone(kcContext),
(() => {
const mock = deepClone(kcContextCommonMock);
Object.assign(mock, { pageId, aNonStandardValue });
return mock;
})()
));
console.log(`PASS ${pageId}`);
}
{
const pageId = "my-extra-page-1.ftl";
console.log("We expect a warning here =>");
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
//@ts-expect-error
doExtends<Any.Equals<typeof kcContext, any>, 1>();
assert(kcContext?.pageId === pageId);
//@ts-expect-error
doExtends<typeof kcContext, KcContextBase>();
doExtends<typeof kcContext, KcContextBase.Common>();
assert(same(
deepClone(kcContext),
(() => {
const mock = deepClone(kcContextCommonMock);
Object.assign(mock, { pageId });
return mock;
})()
));
console.log(`PASS ${pageId}`);
}
{
const { kcContext } = getKcContext();
//@ts-expect-error
doExtends<Any.Equals<typeof kcContext, any>, 1>();
doExtends<typeof kcContext, KcContextBase | undefined>();
}

2
src/test/lib/index.ts Normal file
View File

@ -0,0 +1,2 @@
import "./getKcContext";

View File

@ -0,0 +1,91 @@
import { AndByDiscriminatingKey } from "../../../lib/tools/AndByDiscriminatingKey";
import { doExtends } from "tsafe/doExtends";
type Base =
{ pageId: "a"; onlyA: string; } |
{ pageId: "b"; onlyB: string; } |
{ pageId: "only base"; onlyBase: string; };
type Extension =
{ pageId: "a"; onlyExtA: string; } |
{ pageId: "b"; onlyExtB: string; } |
{ pageId: "only ext"; onlyExt: string; };
type Got = AndByDiscriminatingKey<"pageId", Extension, Base>;
type Expected =
{ pageId: "a"; onlyA: string; onlyExtA: string; } |
{ pageId: "b"; onlyB: string; onlyExtB: string; } |
{ pageId: "only base"; onlyBase: string; } |
{ pageId: "only ext"; onlyExt: string; };
doExtends<Got, Expected>();
doExtends<Expected, Got>();
const x: Got = null as any;
if (x.pageId === "a") {
x.onlyA;
x.onlyExtA;
//@ts-expect-error
x.onlyB;
//@ts-expect-error
x.onlyBase;
//@ts-expect-error
x.onlyExt;
}
if (x.pageId === "b") {
x.onlyB;
x.onlyExtB;
//@ts-expect-error
x.onlyA;
//@ts-expect-error
x.onlyBase;
//@ts-expect-error
x.onlyExt;
}
if (x.pageId === "only base") {
x.onlyBase;
//@ts-expect-error
x.onlyA;
//@ts-expect-error
x.onlyB;
//@ts-expect-error
x.onlyExt;
}
if (x.pageId === "only ext") {
x.onlyExt;
//@ts-expect-error
x.onlyA;
//@ts-expect-error
x.onlyB;
//@ts-expect-error
x.onlyBase;
}

View File

@ -514,6 +514,15 @@ evt@2.0.0-beta.15:
minimal-polyfills "^2.1.5"
run-exclusive "^2.2.14"
evt@2.0.0-beta.20:
version "2.0.0-beta.20"
resolved "https://registry.yarnpkg.com/evt/-/evt-2.0.0-beta.20.tgz#a018ff46a7dcb475c488e6508ac3aec7910e76fb"
integrity sha512-aIIqrDkQ5Hm4zzW7v9STZbO9RcwAI2vMQUkjQ7Bw4YkWOv2zoIl6ooJZ6942JU7UCYKWSi1u5BBtyoiXUSsnMA==
dependencies:
minimal-polyfills "^2.2.1"
run-exclusive "^2.2.14"
tsafe "^0.2.2"
ext@^1.1.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
@ -770,7 +779,7 @@ micromark@~2.11.0:
debug "^4.0.0"
parse-entities "^2.0.0"
minimal-polyfills@^2.1.5, minimal-polyfills@^2.1.6:
minimal-polyfills@^2.1.5, minimal-polyfills@^2.1.6, minimal-polyfills@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/minimal-polyfills/-/minimal-polyfills-2.2.1.tgz#7249d7ece666d3b4e1ec1c1b8f949eb9d44e2308"
integrity sha512-WLmHQrsZob4rVYf8yHapZPNJZ3sspGa/sN8abuSD59b0FifDEE7HMfLUi24z7mPZqTpBXy4Svp+iGvAmclCmXg==
@ -893,10 +902,10 @@ path@^0.12.7:
process "^0.11.1"
util "^0.10.3"
powerhooks@^0.1.0:
version "0.1.4"
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.1.4.tgz#82aae8dae5485a154d3ce4e89342a2d68cb2a413"
integrity sha512-ig47hJIW/b75gCS3l2EtK8NVAduNVd9vem8IvaWkuPuZIP4mbDAy4Rc0/hvjXVUPs9OzK7jc/zIQika3tTacYg==
powerhooks@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.1.6.tgz#23dfb6c781bbfdd20ea37ff5a2a0fd5a9799d591"
integrity sha512-S1b5awSAimyt9jXZsm+N8/GFBtzLPzmVIUyVLq/IADDe4aAcxFWBgXSWTOFc31PlWE9uz2K/NovsQNDf4Fr52A==
dependencies:
evt "2.0.0-beta.15"
memoizee "^0.4.15"
@ -1121,11 +1130,26 @@ trough@^1.0.0:
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
ts-toolbelt@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5"
integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==
tsafe@^0.1.0:
version "0.1.15"
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.1.15.tgz#9e2b6137fb5a49fc7c23cb0bc49ae09bab215f2a"
integrity sha512-aAWMOACHXMmwE2zRcQpBv+whxYqB4zvAbs+dzwbnGaGK9NAOQ65m0+WyO+jw/41JzCX7orJU/ieFHTmgehTOKA==
tsafe@^0.2.2:
version "0.2.4"
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.2.4.tgz#f2cffb9b33e4d20c6f9800aa1c45bee02d917aa1"
integrity sha512-oS/0wJKgEv/iDow/6U51xjAascJVtUQfZQMCDdoOS+VnMlWZFdXGMKuoH47JYs6hEWHpO+Z5pC5oAFFHiPfTLg==
tsafe@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.4.1.tgz#00af1be2db82abb4be531209b90232d7954e1a03"
integrity sha512-+OZ0gdgmwcru+MOSheCx+ymAvQz+1/ui+KFJRuaq0t2m8RNrlf7eSzEieptoPQXPY67Mdkqgkdjknn8azoD5sw==
tslib@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"