While working on the manufacturing floor at Beckett Gas, I was looking for an IT job. My expertise is in programming. The main languages I use are C, C++, C#, D and Javascript/Typescript.
Before coming to Hyland for an interview, I built a private web panel in D, Javascript and Typescript. It communicates with another server and manages actions to execute for users.
I decided to use Typescript for this project and created something special just for this. The following is a type-safe reducer creator written specifically for the version of the language out at the time.
// tslint:disable:prefer-object-spread
// tslint:disable:no-object-literal-type-assertion
// Action Types
import { connect } from 'react-redux';
type EmptyAction<T extends string> = {
type: T;
};
type PayloadAction<T extends string, P> = {
type: T;
payload: P;
error?: boolean;
};
type MetaAction<T extends string, P, M> = {
type: T;
payload: P;
meta: M;
error?: boolean;
};
type Action<T extends string, P = {}, M = {}> = EmptyAction<T> | PayloadAction<T, P> | MetaAction<T, P, M>;
// Action Creator Types
interface BaseActionCreator<T extends string> {
actionName: T;
toString: () => T;
}
interface EmptyActionCreator<T extends string> extends BaseActionCreator<T> {
(): EmptyAction<T>;
type: EmptyAction<T>;
}
interface PayloadActionCreator<T extends string, P> extends BaseActionCreator<T> {
(payload: P): PayloadAction<T, P>;
type: PayloadAction<T, P>;
}
interface OwnPayloadActionCreator<T extends string, P> extends BaseActionCreator<T> {
type: PayloadAction<T, P>;
}
interface MetaActionCreator<T extends string, P, M> extends BaseActionCreator<T> {
(payload: P, meta: M): MetaAction<T, P, M>;
type: MetaAction<T, P, M>;
}
interface OwnMetaActionCreator<T extends string, P, M> extends BaseActionCreator<T> {
type: MetaAction<T, P, M>;
}
type ActionCreator<T extends string, P, M> = EmptyActionCreator<T> |
PayloadActionCreator<T, P> |
OwnPayloadActionCreator<T, P> |
MetaActionCreator<T, P, M> |
OwnMetaActionCreator<T, P, M>;
type OwnPayloadBuilderFunction<T extends string, P> = <
F extends (...a: any[]) => PayloadAction<T, P>
>(func: F) => OwnPayloadActionBuilder<T, P, F>;
type OwnMetaBuilderFunction<T extends string, P, M> = <
F extends (...a: any[]) => MetaAction<T, P, M>
>(func: F) => OwnMetaActionBuilder<T, P, M, F>;
type EmptyActionBuilder<T extends string> = {
payload: <P>() => PayloadActionBuilder<T, P>;
meta: <P, M>() => MetaActionBuilder<T, P, M>;
ownPayload: <P>() => OwnPayloadBuilderFunction<T, P>;
ownMeta: <P, M>() => OwnMetaBuilderFunction<T, P, M>;
to: EmptyActionCreator<T>;
};
type PayloadActionBuilder<T extends string, P> = {
to: PayloadActionCreator<T, P>;
};
type OwnPayloadActionBuilder<T extends string, P, F> = {
to: F & OwnPayloadActionCreator<T, P>;
};
type MetaActionBuilder<T extends string, P, M> = {
to: MetaActionCreator<T, P, M>;
};
type OwnMetaActionBuilder<T extends string, P, M, F> = {
to: F & OwnMetaActionCreator<T, P, M>;
};
type ActionBuilder<T extends string, P, M, F> = EmptyActionBuilder<T> |
PayloadActionBuilder<T, P> |
OwnPayloadActionBuilder<T, P, F> |
MetaActionBuilder<T, P, M> |
OwnMetaActionBuilder<T, P, M, F>;
export type ActionUnion<
T extends { [k: string]: ActionCreator<any, any, any> },
K extends keyof T = keyof T,
> = T[K]['type'];
export function action<T extends string, P, M>(type: T): EmptyAction<T>;
export function action<T extends string, P, M>(type: T, payload: P): PayloadAction<T, P>;
export function action<T extends string, P, M>(type: T, payload: P, meta: M): MetaAction<T, P, M>;
export function action<T extends string, P, M>(type: T, payload?: P, meta?: M): Action<T, P, M> {
if (meta) {
if (payload instanceof Error) {
return ({ type, payload, meta, error: true });
} else {
return ({ type, payload, meta });
}
} else if (payload) {
if (payload instanceof Error) {
return ({ type, payload, error: true });
} else {
return ({ type, payload });
}
} else {
return ({ type });
}
}
export const actionCreator = <T extends string>(type: T): EmptyActionBuilder<T> => {
return Object.assign(
(): EmptyAction<T> => action(type),
{
payload: <P>(): PayloadActionBuilder<T, P> => {
return Object.assign(
(payload: P): PayloadAction<T, P> => action(type, payload),
{
to: undefined as any as PayloadActionCreator<T, P>,
actionName: type,
toString: () => type,
type: undefined as any as PayloadAction<T, P>
}
);
},
ownPayload: <P>(): OwnPayloadBuilderFunction<T, P> => {
return <F extends (...a: any[]) => PayloadAction<T, P>>(func: F) => {
return Object.assign(
func,
{
to: undefined as any as F & OwnPayloadActionCreator<T, P>,
actionName: type,
toString: () => type,
type: undefined as any as PayloadAction<T, P>
}
);
};
},
meta: <P, M>(): MetaActionBuilder<T, P, M> => {
return Object.assign(
(payload: P, meta: M): MetaAction<T, P, M> => action(type, payload, meta),
{
to: undefined as any as MetaActionCreator<T, P, M>,
actionName: type,
toString: () => type,
type: undefined as any as MetaAction<T, P, M>
}
);
},
ownMeta: <P, M>(): OwnMetaBuilderFunction<T, P, M> => {
return <F extends (...a: any[]) => MetaAction<T, P, M>>(func: F) => {
return Object.assign(
func,
{
to: undefined as any as F & OwnMetaActionCreator<T, P, M>,
actionName: type,
toString: () => type,
type: undefined as any as MetaAction<T, P, M>
}
);
};
},
to: {} as EmptyActionCreator<T>,
actionName: type,
toString: () => type,
type: {} as EmptyAction<T>
}
);
};
const isBaseBuilder = <T extends string>(
builder: ActionBuilder<T, any, any, any>
): builder is EmptyActionBuilder<T> => {
return (builder as any).payload != null;
};
export const createActionTable = <
T extends {
[k: string]: ActionBuilder<any, any, any, any>
},
K extends keyof T = keyof T
>(
table: T
): {[P in keyof T]: T[P]['to']} => {
Object.values(table).forEach(obj => {
if (isBaseBuilder(obj)) {
delete obj.payload;
delete obj.ownPayload;
delete obj.meta;
delete obj.ownMeta;
delete obj.to;
} else {
delete obj.to;
}
});
return table as any as {[P in keyof T]: T[P]['to']};
};
// STORE UTILS
export type Dispatch<STORE> = {
<R>(async: (dispatch: Dispatch<STORE>, getState?: () => STORE) => R): R
(action: Action<any, any, any>): void;
};
export const connectFactory = <STORE>() => {
return ({
sfcConnect: <PROPS>() => <STATE, DISPATCH>(
mapStateToProps: (state: STORE, props?: PROPS) => STATE,
mapDispatchToProps?: DISPATCH | ((dispatch: Dispatch<STORE>, props?: PROPS) => DISPATCH)
) => (component: React.SFC<PROPS & STATE & DISPATCH>) => {
return connect(mapStateToProps, mapDispatchToProps as any)(component) as any as React.SFC<PROPS>;
},
classConnect: <PROPS>() => <STATE, DISPATCH>(
mapStateToProps: (state: STORE, props: PROPS) => STATE,
mapDispatchToProps?: DISPATCH | ((dispatch: Dispatch<STORE>, props: PROPS) => DISPATCH)
) => ({
propsType: {} as PROPS & STATE & DISPATCH,
connect: (component: React.ComponentClass<PROPS & STATE & DISPATCH>) =>
connect(mapStateToProps, mapDispatchToProps as any)(component) as any as React.ComponentClass<PROPS>
})
});
};
I’ll finish this post later and add in some more details. I want to take a screenshot of what this actually allows for in the IDE.