Skip to content

Instantly share code, notes, and snippets.

@maticzav
Created February 28, 2024 19:12
Show Gist options
  • Save maticzav/13dd0b9392672af75da793e7b66eee7c to your computer and use it in GitHub Desktop.
Save maticzav/13dd0b9392672af75da793e7b66eee7c to your computer and use it in GitHub Desktop.
import { ExhaustiveSwitchCheck } from '@backend/common'
import { IActionExecution } from './execution'
import { ActionDef, ActionDefPayload, ActionDefResultDict } from './utils'
/**
* A generic interface for a service that can dispatch actions.
*/
export type IDispatcherDefinition<Definitions extends ActionDef> = {
[K in keyof Definitions]: (params: {
data: Definitions[K]['payload']
execution: IActionExecution
}) => Promise<Definitions[K]['result']>
}
// Playground ----------------------------------------------------------------
// Foo
export type FooActions = {
/**
* Creates a new Country and returns it.
*/
create: {
payload: 'create'
result: 'created'
}
/**
* Creates multiple new Countries and returns them.
*/
createMany: {
payload: 'createMany'
result: 'createdMany'
}
}
export interface IFooUpdateService extends IDispatcherDefinition<FooActions> {}
export class FooUpdateService implements IFooUpdateService {
/**
* Creates a new Country and returns it.
*/
public async create({ data, execution }: { data: 'create'; execution: IActionExecution }): Promise<'created'> {
return 'created'
}
/**
* Creates multiple new Countries and returns them.
*/
public async createMany(p: { data: 'createMany'; execution: IActionExecution }): Promise<'createdMany'> {
return 'createdMany'
}
}
type FooScope = 'foo'
// Bar
export type BarActions = {
/**
* Creates a new Country and returns it.
*/
delete: {
payload: 'delete'
result: 'deleted'
}
/**
* deletes multiple new Countries and returns them.
*/
deleteMany: {
payload: 'deleteMany'
result: 'deletedMany'
}
}
export interface IBarUpdateService extends IDispatcherDefinition<BarActions> {}
export class BarUpdateService implements IBarUpdateService {
/**
* Creates a new Country and returns it.
*/
public async delete({ data, execution }: { data: 'delete'; execution: IActionExecution }): Promise<'deleted'> {
return 'deleted'
}
/**
* deletes multiple new Countries and returns them.
*/
public async deleteMany(p: { data: 'deleteMany'; execution: IActionExecution }): Promise<'deletedMany'> {
return 'deletedMany'
}
}
type BarScope = 'bar'
// Simulate Dispatcher Service ------------------------------------------------------------------------------
type ActionResult = {
foo: ActionDefResultDict<FooActions>
bar: ActionDefResultDict<BarActions>
}
/**
* Extracts the result type of an action based on the inferred scope and type.
*/
type ResultOfAction<A extends Payloads> = A extends { scope: infer Scope; type: infer Type }
? Scope extends keyof ActionResult
? Type extends keyof ActionResult[Scope]
? ActionResult[Scope][Type]
: never
: never
: never
type Payloads = ActionDefPayload<FooScope, FooActions> | ActionDefPayload<BarScope, BarActions>
const fooService = new FooUpdateService()
const barService = new BarUpdateService()
async function execute<A extends Payloads>({
action,
execution,
}: {
action: A
execution: IActionExecution
}): Promise<ResultOfAction<A>> {
switch (action.scope) {
case 'foo': {
const method = fooService[action.type] as (params: {
data: A['payload']
execution: IActionExecution
}) => Promise<ResultOfAction<A>>
return method({ data: action.payload, execution })
}
case 'bar': {
const method = barService[action.type] as (params: {
data: A['payload']
execution: IActionExecution
}) => Promise<ResultOfAction<A>>
return method({ data: action.payload, execution })
}
default:
throw new ExhaustiveSwitchCheck(action)
}
}
async function dispatch<A extends Payloads>({ action, user }: { action: A; user: {} }): Promise<ResultOfAction<A>> {
const execution = {} as unknown as IActionExecution
try {
const result = await execute({ action, execution })
await execution.success(result)
return result
} catch (e) {
await execution.error(e)
throw e
}
}
const result = dispatch({ action: { scope: 'foo', type: 'create', payload: 'create' }, user: {} })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment