Skip to content

Instantly share code, notes, and snippets.

@weeksie
Last active November 29, 2021 14:14
Show Gist options
  • Save weeksie/26b9ebd23088c7abbb8ad8c7325ce027 to your computer and use it in GitHub Desktop.
Save weeksie/26b9ebd23088c7abbb8ad8c7325ce027 to your computer and use it in GitHub Desktop.
import {
Id,
UUID,
Expand,
Require,
} from './types';
import {
isUUID
} from './uuids';
declare global {
/**
Metadata interface is composition over inheritance.
*/
export interface Meta {
session?: string;
receipt?: UUID;
}
}
export interface Action<T extends string, P extends unknown = never> {
type: T;
payload: P;
meta?: Meta;
error?: Error | string;
// Database properties
id?: Id;
version?: number;
sequence?: number;
sourceId?: Id;
createdAt?: Date;
}
export type BaseAction = Action<string, unknown>;
export const isBaseAction = (a: any): a is BaseAction =>
typeof a.type === 'string' && !isActionCreator(a);
/**
utility type for creating an action that shares the same payload as another action
usage:
type FnordCommand = Action<'fnord/command', { fnord: Fnord }>;
type FnordEvent = MirrorEvent<'fnord/event', FnordCommand>;
*/
export type MirrorEvent<T extends string, Command extends BaseAction> =
Action<T, GetPayload<Command>>;
type PayloadArgs<P = never> = [P] extends [never] ? [] : [P];
type ActionCreatorFactory = <T extends string, P = never>(type: T) =>
ActionCreator<T, P> extends { type: T } ? ActionCreator<T, P> : never;
export interface ActionCreator<T extends string, P = never> {
(...args: PayloadArgs<P>): Action<T, P>;
is: (a: any) => a is Action<T, P>;
type: T;
namespace: string;
}
export type NamespacedAction<N extends string, P> = Action<`${N}/${string}`, P>;
export const isActionCreator = <T extends string, P = never>(a: any): a is ActionCreator<T, P> =>
typeof a.type === 'string' && typeof a.is === 'function';
export const create: ActionCreatorFactory = <T extends string, P = never>(type: T) => {
const creator = (...args: PayloadArgs<P>): Action<T, P> => {
return {
type,
payload: args[0],
};
};
creator.is = (a: any): a is Action<T, P> => a.type === type;
creator.type = type;
creator.namespace = type.split('/')[0];
return creator;
}
/**
Usage:
const create = Actions.c<EditorAction>();
*/
export const c = <
Base extends BaseAction,
>() => <A extends Base, P = GetPayload<A>>(
type: A['type']
) =>
create<A['type'], P>(type);
export const withMeta = <A extends BaseAction>(meta: Meta) => (action: A) => ({
...action,
meta: {
...action.meta || {},
...meta
},
});
export const withSession = <A extends BaseAction>(session: string) => withMeta<A>({ session });
export const withSourceId = <A extends BaseAction>(sourceId: Id) => (action: A) => ({
...action,
sourceId,
});
export const withVersion = <A extends BaseAction>(version: number) => (action: A) => ({
...action,
version
});
export const withError = <A extends BaseAction>(error: Error) => (action: A) => ({
...action,
error
});
export const withReceipt = <A extends BaseAction>(receipt: UUID) => withMeta<A>({ receipt });
import * as Actions from 'actions';
...
export type SignUp = Actions.Action<'auth/signUp', Credentials>;
export type SignedUp = Actions.Action<'auth/signedUp', {
user: User;
passwordHash: string;
}>;
export type SignUpFailed = Actions.Action<'auth/signUpFailed', string>;
export type LogIn = Actions.Action<'auth/logIn', Credentials>;
export type LoggedIn = Actions.Action<'auth/loggedIn', {
user: User;
token: SerializedToken;
}>;
export type LogInFailed = Actions.Action<'auth/logInFailed', string>;
export type LogOut = Actions.Action<'auth/logOut'>;
export type LoggedOut = Actions.Action<'auth/loggedOut'>;
export type NotAuthorized = Actions.Action<'auth/notAuthorized', Error>;
export type AuthCommand =
SignUp
| LogIn
| LogOut;
export type AuthEvent =
SignedUp
| SignUpFailed
| LoggedIn
| LogInFailed
| LoggedOut
| NotAuthorized;
export type Action = AuthCommand | AuthEvent;
const create = Actions.c<Action>();
export const commands = {
signUp: create<SignUp>('auth/signUp'),
logIn: create<LogIn>('auth/logIn'),
logOut: create<LogOut>('auth/logOut'),
};
export const events = {
signedUp: create<SignedUp>('auth/signedUp'),
signUpFailed: create<SignUpFailed>('auth/signUpFailed'),
loggedIn: create<LoggedIn>('auth/loggedIn'),
logInFailed: create<LogInFailed>('auth/logInFailed'),
loggedOut: create<LoggedOut>('auth/loggedOut'),
notAuthorized: create<NotAuthorized>('auth/notAuthorized'),
};
const commandTypes = new Set<string>(Object.values(commands).map(c => c.type));
const eventTypes = new Set<string>(Object.values(events).map(e => e.type));
export const isAuthAction = (a: any): a is Action =>
typeof a.type === 'string' && a.type.startsWith('auth/');
export const isAuthCommand = (a: any): a is AuthCommand =>
isAuthAction(a) && commandTypes.has(a.type);
export const isAuthEvent = (a: any): a is AuthEvent =>
isAuthAction(a) && eventTypes.has(a.type);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment