Skip to content

Serves an aggregate over HTTP with effect/http-api: one POST endpoint per command, and GET endpoints for history and, given a schema of the state, state.

const TodoApi = CommandApi.group(Todo, {
state: TodoStateSchema,
errors: [TodoAlreadyCreated, TodoNotFound, TodoAlreadyDeleted]
})
class Api extends HttpApi.make("api").add(TodoApi) {}
const TodoHandlers = CommandApi.layer(Api, "todo", AggregateService.make(Todo))
Endpoint Does
POST /todo/:aggregateId/createTodo Runs createTodo with the body as payload
GET /todo/:aggregateId state, with an optional finishTime
GET /todo/:aggregateId/history history, with optional payload, startTime, finishTime, afterVersion and limit

The group is an ordinary HttpApiGroup, so it takes middleware, a different prefix and OpenAPI annotations like any other, and HttpApiClient calls it.

A command’s response carries the store position of its last event, in the body and in an Event-Position header. ReadAfter is middleware for your own query endpoints: a request with a Read-After header waits until the view models have handled that position, so a client reads what it just wrote.

Defined in: src/CommandApi.ts:82

A command handler failed with an error that errors doesn’t declare. Its tag and message are kept, and the rest of it is left out of the response.

  • object & YieldableError<this>

new CommandRejected(…args): CommandRejected

Defined in: node_modules/.pnpm/effect@4.0.6/node_modules/effect/dist/Schema.d.ts:10979

…[props: object, options?: MakeOptions]

CommandRejected

Schema.TaggedError<CommandRejected>()("CommandRejected", { aggregateName: Schema.String, command: Schema.String, /** The tag of the handler's error. */ error: Schema.String, message: Schema.String }, { httpApiStatus: 422 }).constructor

readonly aggregateName: string = Schema.String

Defined in: src/CommandApi.ts:83

Schema.TaggedError<CommandRejected>()("CommandRejected", { aggregateName: Schema.String, command: Schema.String, /** The tag of the handler's error. */ error: Schema.String, message: Schema.String }, { httpApiStatus: 422 }).aggregateName

readonly command: string = Schema.String

Defined in: src/CommandApi.ts:84

Schema.TaggedError<CommandRejected>()("CommandRejected", { aggregateName: Schema.String, command: Schema.String, /** The tag of the handler's error. */ error: Schema.String, message: Schema.String }, { httpApiStatus: 422 }).command

readonly error: string = Schema.String

Defined in: src/CommandApi.ts:86

The tag of the handler’s error.

Schema.TaggedError<CommandRejected>()("CommandRejected", { aggregateName: Schema.String, command: Schema.String, /** The tag of the handler's error. */ error: Schema.String, message: Schema.String }, { httpApiStatus: 422 }).error

message: string = Schema.String

Defined in: src/CommandApi.ts:87

Schema.TaggedError<CommandRejected>()("CommandRejected", { aggregateName: Schema.String, command: Schema.String, /** The tag of the handler's error. */ error: Schema.String, message: Schema.String }, { httpApiStatus: 422 }).message


Defined in: src/CommandApi.ts:375

The view models didn’t reach the position a Read-After header asked for in time, because they are behind or stalled on a failing handler. Answered with 503: the client can try again, or read without the header and accept an older answer.

  • object & YieldableError<this>

new NotCaughtUp(…args): NotCaughtUp

Defined in: node_modules/.pnpm/effect@4.0.6/node_modules/effect/dist/Schema.d.ts:10979

…[props: object, options?: MakeOptions]

NotCaughtUp

Schema.TaggedError<NotCaughtUp>()("NotCaughtUp", { /** The position the request asked for. */ position: Schema.Int, message: Schema.String }, { httpApiStatus: 503 }).constructor

message: string = Schema.String

Defined in: src/CommandApi.ts:378

Schema.TaggedError<NotCaughtUp>()("NotCaughtUp", { /** The position the request asked for. */ position: Schema.Int, message: Schema.String }, { httpApiStatus: 503 }).message

readonly position: number = Schema.Int

Defined in: src/CommandApi.ts:377

The position the request asked for.

Schema.TaggedError<NotCaughtUp>()("NotCaughtUp", { /** The position the request asked for. */ position: Schema.Int, message: Schema.String }, { httpApiStatus: 503 }).position


Defined in: src/CommandApi.ts:398

Middleware for query endpoints that read view models. A request with a Read-After: <position> header waits until every view model of this process has handled that position, the one a command answered with, before the endpoint runs. A request without the header runs at once. A header that isn’t a position is answered with 400.

const Catalog = HttpApiGroup.make("catalog")
.add(HttpApiEndpoint.get("available", "/books/available", { success: Books }))
.middleware(CommandApi.ReadAfter)
const ReadAfterLive = CommandApi.layerReadAfter({ timeout: "2 seconds" })

Implement it with layerReadAfter.

  • Shape<"effect-cqrs/CommandApi/ReadAfter", HttpApiMiddleware<never, readonly [typeof NotCaughtUp, typeof BadRequest], never>, this> & object

new ReadAfter(_): ReadAfter

Defined in: node_modules/.pnpm/effect@4.0.6/node_modules/effect/dist/http-api/HttpApiMiddleware.d.ts:265

never

ReadAfter

HttpApiMiddleware.Service<ReadAfter>()("effect-cqrs/CommandApi/ReadAfter", { error: [NotCaughtUp, HttpApiError.BadRequest] }).constructor

Defined in: src/CommandApi.ts:276

readonly optional historyLimit?: number

Defined in: src/CommandApi.ts:282

The most events one history response lists, so that a long stream can’t make one response as large as the stream. A client reads further with afterVersion. Defaults to 1,000.


Defined in: src/CommandApi.ts:176

Name extends string

State extends Schema.Top

Errors extends ReadonlyArray<Schema.Top>

readonly optional errors?: Errors

Defined in: src/CommandApi.ts:192

Errors of the command handlers to answer with as they are. Others become CommandRejected. An error without an httpApiStatus annotation is answered with 422.

readonly optional name?: Name

Defined in: src/CommandApi.ts:182

The identifier of the group, the aggregate’s name by default.

readonly optional prefix?: `/${string}`

Defined in: src/CommandApi.ts:184

Where the endpoints start, /<aggregate name> by default.

readonly optional state?: State

Defined in: src/CommandApi.ts:186

The schema of the state. Without it there is no state endpoint.


Defined in: src/CommandApi.ts:402

readonly optional timeout?: Input

Defined in: src/CommandApi.ts:404

How long a request waits before it is answered with NotCaughtUp. Defaults to 5 seconds.

Endpoints<A, State, Errors> = { readonly [K in CommandName<A>]: CommandEndpoint<A, K, Errors> }[CommandName<A>] | HistoryEndpoint<A> | [State] extends [never] ? never : StateEndpoint<State>

Defined in: src/CommandApi.ts:171

The endpoints of group.

A extends Any

State extends Schema.Top

Errors extends Schema.Top

const ConcurrencyConflictError: decodeTo<declareConstructor<ConcurrencyConflict, { aggregateId: string; aggregateName: string; expectedVersion: number; }, readonly [TaggedStruct<"ConcurrencyConflict", { aggregateId: String; aggregateName: String; expectedVersion: Int; }>], { aggregateId: string; aggregateName: string; expectedVersion: number; }>, TaggedStruct<"ConcurrencyConflict", { aggregateId: String; aggregateName: String; expectedVersion: Int; }>, never, never>

Defined in: src/CommandApi.ts:73

The stream moved on while the command was decided, and retries ran out. Answered with 409.


const InvalidCommandError: decodeTo<declareConstructor<InvalidCommand, { aggregateName: string; message: string; type: string; }, readonly [TaggedStruct<"InvalidCommand", { aggregateName: String; message: String; type: String; }>], { aggregateName: string; message: string; type: string; }>, TaggedStruct<"InvalidCommand", { aggregateName: String; message: String; type: String; }>, never, never>

Defined in: src/CommandApi.ts:70

A command whose payload doesn’t match its schema, or a body that isn’t JSON. The same error as AggregateService.InvalidCommand, answered with 400.


const WrongExpectedVersionError: decodeTo<declareConstructor<WrongExpectedVersion, { actualVersion: number; aggregateId: string; aggregateName: string; expectedVersion: number; }, readonly [TaggedStruct<"WrongExpectedVersion", { actualVersion: Int; aggregateId: String; aggregateName: String; expectedVersion: Int; }>], { actualVersion: number; aggregateId: string; aggregateName: string; expectedVersion: number; }>, TaggedStruct<"WrongExpectedVersion", { actualVersion: Int; aggregateId: String; aggregateName: String; expectedVersion: Int; }>, never, never>

Defined in: src/CommandApi.ts:76

The stream is no longer at the version If-Match named. Answered with 409.

commandResult<Events>(events): Struct<{ aggregateId: String; aggregateName: String; correlationId: String; events: $Array<Events>; position: Int; version: Int; }>

Defined in: src/CommandApi.ts:91

The body of a successful command, CommandResult with the events in their JSON form.

Events extends EventSchema<any>

Events

Struct<{ aggregateId: String; aggregateName: String; correlationId: String; events: $Array<Events>; position: Int; version: Int; }>


group<A, Name, State, Errors>(aggregate, options?): HttpApiGroup<Name, Endpoints<A, State, Errors[number]>>

Defined in: src/CommandApi.ts:205

Builds the endpoints of an aggregate. Add the group to an HttpApi and implement it with layer.

A extends Any

Name extends string = A["name"]

State extends Top = never

Errors extends readonly Top[] = []

A

Options<Name, State, Errors>

HttpApiGroup<Name, Endpoints<A, State, Errors[number]>>


historyEntry<Events>(events): Struct<{ causationId: NullOr<String>; correlationId: NullOr<String>; metadata: Codec<JsonObject, JsonObject, never, never>; payload: optionalKey<Events>; timestamp: DateTimeUtc; type: String; version: Int; }>

Defined in: src/CommandApi.ts:102

One entry of history. payload is there when the request asks for it.

Events extends EventSchema<any>

Events

Struct<{ causationId: NullOr<String>; correlationId: NullOr<String>; metadata: Codec<JsonObject, JsonObject, never, never>; payload: optionalKey<Events>; timestamp: DateTimeUtc; type: String; version: Int; }>


layer<ApiId, Groups, Name, A, E, R>(api, name, service, options?): Layer<Service<ApiId, Name>, E, Exclude<R, Scope>>

Defined in: src/CommandApi.ts:292

Implements a group built by group with an aggregate service, or an effect that builds one when the layer starts.

Unexpected failures, such as a store that can’t be reached, are defects and answered with 500.

ApiId extends string

Groups extends Constraint

Name extends string

A extends Any

E = never

R = never

HttpApi<ApiId, Groups>

Name

AggregateService<A> | Effect<AggregateService<A>, E, R>

LayerOptions

Layer<Service<ApiId, Name>, E, Exclude<R, Scope>>


layerReadAfter(options?): Layer<ReadAfter, never, ViewModels>

Defined in: src/CommandApi.ts:415

Implements ReadAfter with the ViewModels of this process. View models that another process holds are not waited for, as with ViewModels.caughtUp.

ReadAfterOptions

Layer<ReadAfter, never, ViewModels>