puristajs / purista

A typescript framework for building backend services and api in modern, modular and scalable way with event-driven patterns

Home Page:https://purista.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

feat: Add chained invoke functions in commands and subscriptions

sebastianwessel opened this issue · comments

Currently, the context of commands and subscriptions provides the invoke function.

Current

Currently, the builders do not have any information about invocations.
This lack of information is one of the blockers to providing tooling for deployment config generation (like generation of cloud provider permissions in deployment configs).

Also, the business code becomes quickly cluttered, typescript types are suboptimal, and testing is a lot of code overhead.

As an example:

.setCommandFunction(async function (context, payload, _parameter) {
  const resultA = await context.invoke<OutputTypeA, InputPayloadTypeA, InputParameterTypeA>(
      { serviceName: 'ServiceA', serviceVersion: '1', serviceTarget: 'foo' },
      payloadA,
      parameterA,
    )
    
  const resultB = await context.invoke<OutputTypeB, InputPayloadTypeB, InputParameterTypeB>(
      { serviceName: 'ServiceB', serviceVersion: '1', serviceTarget: 'bar' },
      payloadB,
      parameterB,
    )

})

Which can be mocked in tests with

const mockedPayload: PayloadType = { ... }
const mockedParameter: ParameterType = { ... }

const context = getCommandContextMock(mockedPayload, mockedParameter, sandbox)

context.stubs.invoke..callsFake((address: EBMessageAddress) => {
  if(address.serviceName === 'ServiceA') {
    return mockedResponseA
  }
  
  return mockedResponseB
})

New

The new behavior should be chained, and the builders should get and provide information about invocations.

// with response zod schema validation - useful for external untrusted/unknown
.canInvoke<InputPayloadTypeA, InputParameterTypeA>('ServiceA', '1', 'foo', responseOutputSchema)
// with type definition only - without runtime schema validation
.canInvoke<InputPayloadTypeB, InputParameterTypeB, OutputTypeB>('ServiceA', '1', 'bar')
.setCommandFunction(async function (context, payload, _parameter) {
  // short & types are set in builder via type definition
  const resultA = await context.service.ServiceA['1'].foo(payloadA, parameterA)
  
  // short & types are set in builder from given zod schema
  const resultB = await context.service.ServiceB['1'].foo(payloadB, parameterB)
})

Which can be mocked in tests with

const mockedPayload: PayloadType = { ... }
const mockedParameter: ParameterType = { ... }

const context = currentCommandBuilder.getCommandContextMock(mockedPayload, mockedParameter, sandbox)

context.stubs.service.ServiceA['1'].foo.resolves(mockedResponseA)
context.stubs.service.ServiceB['1'].bar.resolves(mockedResponseB)

Relates to "enhance the enrichment of meta-information in builders" #134