From 6bcf555da74d54d70f00f37878d35fa166bb7e4c Mon Sep 17 00:00:00 2001 From: Chris Villa Date: Sun, 1 Sep 2024 13:40:15 +0100 Subject: [PATCH] fix: make Config and Data types more robust Fix several issues resulting in failed builds, only caught when enabling TypeScript's strict mode --- packages/core/components/Puck/context.tsx | 2 +- packages/core/components/Puck/index.tsx | 4 + packages/core/lib/resolve-all-data.ts | 30 ++++-- packages/core/lib/resolve-root-data.ts | 17 +++- packages/core/lib/transform-props.ts | 10 +- packages/core/types/Config.tsx | 108 +++++++++++++--------- 6 files changed, 109 insertions(+), 62 deletions(-) diff --git a/packages/core/components/Puck/context.tsx b/packages/core/components/Puck/context.tsx index 1e0c3d3388..19a2e32fef 100644 --- a/packages/core/components/Puck/context.tsx +++ b/packages/core/components/Puck/context.tsx @@ -17,7 +17,7 @@ import { IframeConfig } from "../../types/IframeConfig"; import { UAParser } from "ua-parser-js"; export const defaultAppState: AppState = { - data: { content: [], root: { props: {} } }, + data: { content: [], root: {} }, ui: { leftSideBarVisible: true, rightSideBarVisible: true, diff --git a/packages/core/components/Puck/index.tsx b/packages/core/components/Puck/index.tsx index 5a6a76c107..4a9edf73d3 100644 --- a/packages/core/components/Puck/index.tsx +++ b/packages/core/components/Puck/index.tsx @@ -60,6 +60,10 @@ import { DefaultOverride } from "../DefaultOverride"; const getClassName = getClassNameFactory("Puck", styles); const getLayoutClassName = getClassNameFactory("PuckLayout", styles); +type ValidateConfig = T extends Config + ? Config + : never; + export function Puck({ children, config, diff --git a/packages/core/lib/resolve-all-data.ts b/packages/core/lib/resolve-all-data.ts index 5fa6c4bff3..05e073ee5d 100644 --- a/packages/core/lib/resolve-all-data.ts +++ b/packages/core/lib/resolve-all-data.ts @@ -1,42 +1,52 @@ -import { Config, Data, MappedItem } from "../types/Config"; +import { + ComponentData, + Config, + Content, + Data, + DefaultComponentProps, + DefaultRootFieldProps, +} from "../types/Config"; import { resolveAllComponentData } from "./resolve-component-data"; import { resolveRootData } from "./resolve-root-data"; import { defaultData } from "./default-data"; -export async function resolveAllData( +export async function resolveAllData< + Props extends DefaultComponentProps = DefaultComponentProps, + RootProps extends Record = DefaultRootFieldProps +>( data: Partial, config: Config, - onResolveStart?: (item: MappedItem) => void, - onResolveEnd?: (item: MappedItem) => void + onResolveStart?: (item: ComponentData) => void, + onResolveEnd?: (item: ComponentData) => void ) { const defaultedData = defaultData(data); - const dynamicRoot = await resolveRootData(defaultedData, config); + const dynamicRoot = await resolveRootData(defaultedData, config); const { zones = {} } = data; const zoneKeys = Object.keys(zones); - const resolvedZones: Record = {}; + const resolvedZones: Record> = {}; for (let i = 0; i < zoneKeys.length; i++) { const zoneKey = zoneKeys[i]; - resolvedZones[zoneKey] = await resolveAllComponentData( + resolvedZones[zoneKey] = (await resolveAllComponentData( zones[zoneKey], config, onResolveStart, onResolveEnd - ); + )) as Content; } return { ...defaultedData, root: dynamicRoot, - content: await resolveAllComponentData( + content: (await resolveAllComponentData( defaultedData.content, config, onResolveStart, onResolveEnd - ), + )) as Content, zones: resolvedZones, }; } diff --git a/packages/core/lib/resolve-root-data.ts b/packages/core/lib/resolve-root-data.ts index f3e38f96d4..43863caf4b 100644 --- a/packages/core/lib/resolve-root-data.ts +++ b/packages/core/lib/resolve-root-data.ts @@ -1,11 +1,18 @@ -import { Config, Data, RootDataWithProps } from "../types/Config"; +import { + Config, + Data, + DefaultRootFieldProps, + RootDataWithProps, +} from "../types/Config"; import { getChanged } from "./get-changed"; export const cache: { lastChange?: { original: RootDataWithProps; resolved: RootDataWithProps }; } = {}; -export const resolveRootData = async (data: Data, config: Config) => { +export async function resolveRootData< + RootProps extends Record = DefaultRootFieldProps +>(data: Data, config: Config) { if (config.root?.resolveData && data.root.props) { if (cache.lastChange?.original === data.root) { return cache.lastChange.resolved; @@ -21,8 +28,8 @@ export const resolveRootData = async (data: Data, config: Config) => { }); cache.lastChange = { - original: data.root as RootDataWithProps, - resolved: resolvedRoot as RootDataWithProps, + original: data.root as RootDataWithProps, + resolved: resolvedRoot as RootDataWithProps, }; return { @@ -36,4 +43,4 @@ export const resolveRootData = async (data: Data, config: Config) => { } return data.root; -}; +} diff --git a/packages/core/lib/transform-props.ts b/packages/core/lib/transform-props.ts index 23c6a24b0b..5ee231a799 100644 --- a/packages/core/lib/transform-props.ts +++ b/packages/core/lib/transform-props.ts @@ -1,9 +1,13 @@ -import { Data, DefaultComponentProps, DefaultRootProps } from "../types/Config"; +import { + Data, + DefaultComponentProps, + DefaultRootFieldProps, +} from "../types/Config"; import { defaultData } from "./default-data"; type PropTransform< Props extends DefaultComponentProps = DefaultComponentProps, - RootProps extends DefaultRootProps = DefaultRootProps + RootProps extends DefaultComponentProps = DefaultRootFieldProps > = Partial< { [ComponentName in keyof Props]: ( @@ -14,7 +18,7 @@ type PropTransform< export function transformProps< Props extends DefaultComponentProps = DefaultComponentProps, - RootProps extends DefaultComponentProps = DefaultComponentProps + RootProps extends DefaultComponentProps = DefaultRootFieldProps >(data: Partial, propTransforms: PropTransform): Data { const mapItem = (item) => { if (propTransforms[item.type]) { diff --git a/packages/core/types/Config.tsx b/packages/core/types/Config.tsx index 61f7ab4271..6bbe1a877c 100644 --- a/packages/core/types/Config.tsx +++ b/packages/core/types/Config.tsx @@ -4,23 +4,42 @@ import { DropZoneProps } from "../components/DropZone/types"; import { Viewport } from "./Viewports"; import { Fields } from "./Fields"; -type WithPuckProps = Props & { +type WithId = Props & { id: string; }; -export type DefaultRootProps = { +type WithPuckProps = WithId & { puck: PuckContext }; +type AsFieldProps = Partial< + Omit +>; + +type WithChildren = Props & { + children: ReactNode; +}; + +export type DefaultRootFieldProps = { title?: string; - [key: string]: any; }; -export type DefaultComponentProps = { [key: string]: any; editMode?: boolean }; +export type DefaultRootRenderProps< + Props extends DefaultComponentProps = DefaultRootFieldProps +> = WithPuckProps>; + +export type DefaultRootProps = DefaultRootRenderProps; // Deprecated + +export type DefaultComponentProps = { [key: string]: any }; export type Content< - Props extends { [key: string]: any } = { [key: string]: any } -> = ComponentData[]; + PropsMap extends { [key: string]: any } = { [key: string]: any } +> = ComponentDataMap[]; export type PuckComponent = ( - props: WithPuckProps + props: WithPuckProps< + Props & { + puck: PuckContext; + editMode?: boolean; // Deprecated + } + > ) => JSX.Element; export type PuckContext = { @@ -29,44 +48,44 @@ export type PuckContext = { }; export type ComponentConfig< - ComponentProps extends DefaultComponentProps = DefaultComponentProps, - DefaultProps = ComponentProps, - DataShape = Omit, "type"> + RenderProps extends DefaultComponentProps = DefaultComponentProps, + FieldProps extends DefaultComponentProps = RenderProps, + DataShape = Omit, "type"> > = { - render: PuckComponent; + render: PuckComponent; label?: string; - defaultProps?: DefaultProps; - fields?: Fields; + defaultProps?: FieldProps; + fields?: Fields; permissions?: Partial; resolveFields?: ( data: DataShape, params: { - changed: Partial>; - fields: Fields; - lastFields: Fields; + changed: Partial>; + fields: Fields; + lastFields: Fields; lastData: DataShape; appState: AppState; } - ) => Promise> | Fields; + ) => Promise> | Fields; resolveData?: ( data: DataShape, params: { - changed: Partial>; + changed: Partial>; lastData: DataShape; } ) => | Promise<{ - props?: Partial; - readOnly?: Partial>; + props?: Partial; + readOnly?: Partial>; }> | { - props?: Partial; - readOnly?: Partial>; + props?: Partial; + readOnly?: Partial>; }; resolvePermissions?: ( data: DataShape, params: { - changed: Partial>; + changed: Partial>; lastPermissions: Partial | undefined; initialPermissions: Partial; appState: AppState; @@ -83,7 +102,7 @@ type Category = { export type Config< Props extends Record = Record, - RootProps extends DefaultRootProps = DefaultRootProps, + RootProps extends DefaultComponentProps = DefaultRootFieldProps, CategoryName extends string = string > = { categories?: Record> & { @@ -97,8 +116,8 @@ export type Config< }; root?: Partial< ComponentConfig< - RootProps & { children?: ReactNode }, - Partial, + DefaultRootRenderProps, + AsFieldProps, RootData > >; @@ -111,42 +130,45 @@ export type BaseData< }; export type ComponentData< - Props extends DefaultComponentProps = DefaultComponentProps + Props extends DefaultComponentProps = DefaultComponentProps, + Name = string > = { - type: keyof Props; - props: WithPuckProps; + type: Name; + props: WithId; } & BaseData; +export type ComponentDataMap< + Props extends Record = DefaultComponentProps +> = { + [K in keyof Props]: ComponentData; +}[keyof Props]; + export type RootDataWithProps< - Props extends DefaultRootProps = DefaultRootProps + Props extends DefaultComponentProps = DefaultRootFieldProps > = BaseData & { props: Props; }; // DEPRECATED export type RootDataWithoutProps< - Props extends DefaultRootProps = DefaultRootProps + Props extends DefaultComponentProps = DefaultRootFieldProps > = Props; -export type RootData = - Partial> & Partial>; // DEPRECATED - -type ComponentDataWithOptionalProps< - Props extends { [key: string]: any } = { [key: string]: any } -> = Omit & { - props: Partial>; -}; +export type RootData< + Props extends DefaultComponentProps = DefaultRootFieldProps +> = Partial>> & + Partial>; // DEPRECATED -// Backwards compatability +// Backwards compatibility export type MappedItem = ComponentData; export type Data< Props extends DefaultComponentProps = DefaultComponentProps, - RootProps extends DefaultRootProps = DefaultRootProps + RootProps extends DefaultComponentProps = DefaultRootFieldProps > = { root: RootData; - content: Content>; - zones?: Record>>; + content: Content; + zones?: Record>; }; export type ItemWithId = {