Skip to content

Commit

Permalink
fix: make Config and Data types more robust
Browse files Browse the repository at this point in the history
Fix several issues resulting in failed builds, only caught when enabling TypeScript's strict mode
  • Loading branch information
chrisvxd committed Sep 3, 2024
1 parent 5a219ef commit 6bcf555
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 62 deletions.
2 changes: 1 addition & 1 deletion packages/core/components/Puck/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions packages/core/components/Puck/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ import { DefaultOverride } from "../DefaultOverride";
const getClassName = getClassNameFactory("Puck", styles);
const getLayoutClassName = getClassNameFactory("PuckLayout", styles);

type ValidateConfig<T> = T extends Config<infer P, infer R, infer C>
? Config<P, R, C>
: never;

export function Puck<UserConfig extends Config = Config>({
children,
config,
Expand Down
30 changes: 20 additions & 10 deletions packages/core/lib/resolve-all-data.ts
Original file line number Diff line number Diff line change
@@ -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<string, any> = DefaultRootFieldProps
>(
data: Partial<Data>,
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<RootProps>(defaultedData, config);

const { zones = {} } = data;

const zoneKeys = Object.keys(zones);
const resolvedZones: Record<string, MappedItem[]> = {};
const resolvedZones: Record<string, Content<Props>> = {};

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<Props>;
}

return {
...defaultedData,
root: dynamicRoot,
content: await resolveAllComponentData(
content: (await resolveAllComponentData(
defaultedData.content,
config,
onResolveStart,
onResolveEnd
),
)) as Content<Props>,
zones: resolvedZones,
};
}
17 changes: 12 additions & 5 deletions packages/core/lib/resolve-root-data.ts
Original file line number Diff line number Diff line change
@@ -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<string, any> = DefaultRootFieldProps
>(data: Data, config: Config) {
if (config.root?.resolveData && data.root.props) {
if (cache.lastChange?.original === data.root) {
return cache.lastChange.resolved;
Expand All @@ -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<RootProps>,
resolved: resolvedRoot as RootDataWithProps<RootProps>,
};

return {
Expand All @@ -36,4 +43,4 @@ export const resolveRootData = async (data: Data, config: Config) => {
}

return data.root;
};
}
10 changes: 7 additions & 3 deletions packages/core/lib/transform-props.ts
Original file line number Diff line number Diff line change
@@ -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]: (
Expand All @@ -14,7 +18,7 @@ type PropTransform<

export function transformProps<
Props extends DefaultComponentProps = DefaultComponentProps,
RootProps extends DefaultComponentProps = DefaultComponentProps
RootProps extends DefaultComponentProps = DefaultRootFieldProps
>(data: Partial<Data>, propTransforms: PropTransform<Props, RootProps>): Data {
const mapItem = (item) => {
if (propTransforms[item.type]) {
Expand Down
108 changes: 65 additions & 43 deletions packages/core/types/Config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,42 @@ import { DropZoneProps } from "../components/DropZone/types";
import { Viewport } from "./Viewports";
import { Fields } from "./Fields";

type WithPuckProps<Props> = Props & {
type WithId<Props> = Props & {
id: string;
};

export type DefaultRootProps = {
type WithPuckProps<Props> = WithId<Props> & { puck: PuckContext };
type AsFieldProps<Props> = Partial<
Omit<Props, "children" | "puck" | "editMode">
>;

type WithChildren<Props> = 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<WithChildren<Props>>;

export type DefaultRootProps = DefaultRootRenderProps; // Deprecated

export type DefaultComponentProps = { [key: string]: any };

export type Content<
Props extends { [key: string]: any } = { [key: string]: any }
> = ComponentData<Props>[];
PropsMap extends { [key: string]: any } = { [key: string]: any }
> = ComponentDataMap<PropsMap>[];

export type PuckComponent<Props> = (
props: WithPuckProps<Props & { puck: PuckContext }>
props: WithPuckProps<
Props & {
puck: PuckContext;
editMode?: boolean; // Deprecated
}
>
) => JSX.Element;

export type PuckContext = {
Expand All @@ -29,44 +48,44 @@ export type PuckContext = {
};

export type ComponentConfig<
ComponentProps extends DefaultComponentProps = DefaultComponentProps,
DefaultProps = ComponentProps,
DataShape = Omit<ComponentData<ComponentProps>, "type">
RenderProps extends DefaultComponentProps = DefaultComponentProps,
FieldProps extends DefaultComponentProps = RenderProps,
DataShape = Omit<ComponentData<FieldProps>, "type">
> = {
render: PuckComponent<ComponentProps>;
render: PuckComponent<RenderProps>;
label?: string;
defaultProps?: DefaultProps;
fields?: Fields<ComponentProps>;
defaultProps?: FieldProps;
fields?: Fields<FieldProps>;
permissions?: Partial<Permissions>;
resolveFields?: (
data: DataShape,
params: {
changed: Partial<Record<keyof ComponentProps, boolean>>;
fields: Fields<ComponentProps>;
lastFields: Fields<ComponentProps>;
changed: Partial<Record<keyof FieldProps, boolean>>;
fields: Fields<FieldProps>;
lastFields: Fields<FieldProps>;
lastData: DataShape;
appState: AppState;
}
) => Promise<Fields<ComponentProps>> | Fields<ComponentProps>;
) => Promise<Fields<FieldProps>> | Fields<FieldProps>;
resolveData?: (
data: DataShape,
params: {
changed: Partial<Record<keyof ComponentProps, boolean>>;
changed: Partial<Record<keyof FieldProps, boolean>>;
lastData: DataShape;
}
) =>
| Promise<{
props?: Partial<ComponentProps>;
readOnly?: Partial<Record<keyof ComponentProps, boolean>>;
props?: Partial<FieldProps>;
readOnly?: Partial<Record<keyof FieldProps, boolean>>;
}>
| {
props?: Partial<ComponentProps>;
readOnly?: Partial<Record<keyof ComponentProps, boolean>>;
props?: Partial<FieldProps>;
readOnly?: Partial<Record<keyof FieldProps, boolean>>;
};
resolvePermissions?: (
data: DataShape,
params: {
changed: Partial<Record<keyof ComponentProps, boolean>>;
changed: Partial<Record<keyof FieldProps, boolean>>;
lastPermissions: Partial<Permissions> | undefined;
initialPermissions: Partial<Permissions>;
appState: AppState;
Expand All @@ -83,7 +102,7 @@ type Category<ComponentName> = {

export type Config<
Props extends Record<string, any> = Record<string, any>,
RootProps extends DefaultRootProps = DefaultRootProps,
RootProps extends DefaultComponentProps = DefaultRootFieldProps,
CategoryName extends string = string
> = {
categories?: Record<CategoryName, Category<keyof Props>> & {
Expand All @@ -97,8 +116,8 @@ export type Config<
};
root?: Partial<
ComponentConfig<
RootProps & { children?: ReactNode },
Partial<RootProps & { children?: ReactNode }>,
DefaultRootRenderProps<RootProps>,
AsFieldProps<RootProps>,
RootData
>
>;
Expand All @@ -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<Props>;
type: Name;
props: WithId<Props>;
} & BaseData<Props>;

export type ComponentDataMap<
Props extends Record<string, DefaultComponentProps> = DefaultComponentProps
> = {
[K in keyof Props]: ComponentData<Props[K], K>;
}[keyof Props];

export type RootDataWithProps<
Props extends DefaultRootProps = DefaultRootProps
Props extends DefaultComponentProps = DefaultRootFieldProps
> = BaseData<Props> & {
props: Props;
};

// DEPRECATED
export type RootDataWithoutProps<
Props extends DefaultRootProps = DefaultRootProps
Props extends DefaultComponentProps = DefaultRootFieldProps
> = Props;

export type RootData<Props extends DefaultRootProps = DefaultRootProps> =
Partial<RootDataWithProps<Props>> & Partial<RootDataWithoutProps<Props>>; // DEPRECATED

type ComponentDataWithOptionalProps<
Props extends { [key: string]: any } = { [key: string]: any }
> = Omit<ComponentData, "props"> & {
props: Partial<WithPuckProps<Props>>;
};
export type RootData<
Props extends DefaultComponentProps = DefaultRootFieldProps
> = Partial<RootDataWithProps<AsFieldProps<Props>>> &
Partial<RootDataWithoutProps<Props>>; // 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<RootProps>;
content: Content<WithPuckProps<Props>>;
zones?: Record<string, Content<WithPuckProps<Props>>>;
content: Content<Props>;
zones?: Record<string, Content<Props>>;
};

export type ItemWithId = {
Expand Down

0 comments on commit 6bcf555

Please sign in to comment.