Last active
November 29, 2021 14:14
-
-
Save weeksie/26b9ebd23088c7abbb8ad8c7325ce027 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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