Commands and wait results
CommandClient is a decorator-backed transport. It is not generic: the body type is chosen per call, so one client sends every command of an aggregate. send<C>(request, attributes?) returns Promise<CommandResult> for the stage the command waited for. sendAndWaitStream<C>(request, attributes?) returns Promise<ReadableStream<JsonServerSentEvent<CommandResult>>> — one result per stage the command reaches — with Accept text/event-stream. Supply ApiMetadata and/or a CommandRequest with the endpoint required by your service; no universal command URL is inferred from C.
Requests and wait stages
CommandRequest extends ParameterRequest. Its body is CommandBody<C> (the command's writable fields), with optional path (endpoint path override, not url), method, URL path parameters, headers and other request data. The transport does not synthesize an idempotency key or choose a wait stage for you.
CommandHeaders and WowHeaders are frozen as const objects of header names, so each value is a string literal type. CommandHeaders.SPACE_ID is Wow-Space-Id (WowHeaders.SPACE_ID), which the server shares with queries; Command-Header- is the prefix of custom headers the server copies into the command header. CommandRequestHeaders types every known command header by what the server parses — each is optional:
| Header | Value type |
|---|---|
Command-Wait-Stage, Command-Wait-Tail-Stage | CommandStageName — a CommandStage or its name |
Command-Aggregate-Version, Command-Wait-Timeout | `${number}`, an integer as a string |
Command-Local-First | 'true' or 'false' |
tenant, owner, space, aggregate id, request id, wait context/processor/function, tail selectors, /wow/command/send context/name/type | string |
Any other header (Authorization, a Command-Header-* extension) is still allowed as a string. The generic /wow/command/send route has no method of its own: send to it with CommandClient and the COMMAND_AGGREGATE_CONTEXT, COMMAND_AGGREGATE_NAME and COMMAND_TYPE headers. Build the headers with two helpers instead of by hand; both omit the headers whose options you leave out and throw TypeError on invalid input:
commandHeaders({ tenantId?, ownerId?, spaceId?, aggregateId?, aggregateVersion?, requestId?, localFirst? })says who the command is for.aggregateVersionmust be a non-negative integer;localFirstbecomes'true'/'false'.waitStrategy({ stage?, context?, processor?, function?, timeoutMs?, tail? })says what the server waits for before it answers — Kotlin'sWaitingFor. A stage must be aCommandStage;timeoutMsmust be a positive integer. Without a stage the server waits forPROCESSEDin the command's own bounded context.- A
tailmakes it a wait chain: wait for a saga to handle the command's events (stage: 'SAGA_HANDLED', optionally narrowed byprocessor), then for the command that saga sends to reachtail.stage, optionally intail.context/tail.processor/tail.function. Atailwith any other stage is a type error and throws at runtime, because the server would ignore it.
| CommandStage | Signal represented |
|---|---|
| SENT | Command sent |
| PROCESSED | Command processed |
| SNAPSHOT | Snapshot stage |
| PROJECTED | Projection stage |
| EVENT_HANDLED | Event handler stage |
| SAGA_HANDLED | Saga handler stage |
These values are distinct server wait targets, not a client-side guarantee that every downstream consumer is caught up. The client sets no wait timeout default. A CommandResult carries identity, aggregate attribution, stage, command/request/wait IDs, signal time, optional aggregateVersion, function info, result map and errorCode/errorMsg/bindingErrors. WaitSignal carries the corresponding signal model with nested aggregateId. BatchResult contains after/size and ErrorInfo; it does not implement batch iteration.
Failures
- A request the server refuses (validation, a version conflict, a duplicate request id, …) rejects the Promise;
await toWowError(error)reads the server'sErrorInfointo aWowError. See errors. - A command whose processing failed resolves too, with the failure in the result's
errorCode: compare it withErrorCodes.SUCCEEDED('Ok') before treating the write as completed. sendAndWaitStreamerrors the stream with aWowErrorwhen the server fails midway — for example when the wait times out — so afor awaitover it throws. Results whose ownerrorCodeis notOkare still passed on as results. The consumer must cancel/release a reader on early exit.
DeleteAggregate/RecoverAggregate are empty command-body contracts; resource-tag commands carry tags. They do not delete/recover anything until a server endpoint executes them.
Reading a command result
| Field | Interpretation |
|---|---|
id, commandId, requestId, waitCommandId | Distinct signal/command/request/wait correlation IDs. Do not substitute one for the server's idempotency key. |
contextName, aggregateName, tenantId, aggregateId | Flat aggregate identity in CommandResult; WaitSignal instead nests the AggregateId object. |
stage | The emitted wait stage, not a boolean covering every projection. |
aggregateVersion? | Optional version reported by the service; absence does not establish a visibility barrier. |
signalTime | Numeric signal timestamp. It is not a client timeout duration. |
function | Processor/function metadata; see message metadata. |
result | Service-provided result map, not the command body C. |
errorCode, errorMsg, bindingErrors? | Business outcome and optional field errors; see error classification. |
Use identity and attribution for nested/flat identity details. No result interface assigns defaults or validates JSON. A stream carries successive CommandResult payloads in event.data; its initial HTTP success is not completion of every event or stage.
Complete example
import {
CommandClient,
CommandStage,
ErrorCodes,
commandHeaders,
toWowError,
waitStrategy,
} from '@ahoo-wang/wow-client';
const client = new CommandClient({ basePath: 'user' });
export async function rename(id: string, name: string, version: number) {
try {
const result = await client.send<{ name: string }>({
path: '{id}/rename',
method: 'POST',
urlParams: { path: { id } },
headers: {
...commandHeaders({
aggregateVersion: version,
requestId: crypto.randomUUID(),
}),
...waitStrategy({ stage: CommandStage.SNAPSHOT, timeoutMs: 10_000 }),
},
body: { name },
});
if (result.errorCode !== ErrorCodes.SUCCEEDED) {
throw new Error(`${result.errorCode}: ${result.errorMsg}`);
}
return result;
} catch (error) {
const wowError = await toWowError(error);
if (wowError?.errorCode === ErrorCodes.COMMAND_EXPECT_VERSION_CONFLICT) {
return undefined; // reload the aggregate and let the user retry
}
throw wowError ?? error;
}
}A wait chain — answer once the saga has handled the transfer and the command it sent has been processed in the account context:
import { CommandStage, waitStrategy } from '@ahoo-wang/wow-client';
export const transferWait = waitStrategy({
stage: CommandStage.SAGA_HANDLED,
processor: 'TransferSaga',
tail: { stage: CommandStage.PROCESSED, context: 'account' },
timeoutMs: 30_000,
});Service URLs in examples require application endpoints; type checking does not imply an external service was contacted.
Public signatures and types
These signatures follow declarations reachable from the current root entry. ? marks optional input; generics/interfaces only constrain compile-time types. Locate inherited and related types through the symbol index. Runtime defaults and failure behavior are described above.
CommandClient
export class CommandClient implements ApiMetadataCapable {
constructor(public readonly apiMetadata?: ApiMetadata);
send<C extends object = object>(commandRequest: CommandRequest<C>, attributes?: Record<string, unknown>): Promise<CommandResult>;
sendAndWaitStream<C extends object = object>(commandRequest: CommandRequest<C>, attributes?: Record<string, unknown>): Promise<CommandResultEventStream>;
}typescript/wow-client/src/command/commandClient.ts:59
CommandHeaders
Expand all fields and members
export const CommandHeaders = Object.freeze({
COMMAND_HEADERS_PREFIX: 'Command-',
TENANT_ID: 'Command-Tenant-Id',
OWNER_ID: 'Command-Owner-Id',
SPACE_ID: WowHeaders.SPACE_ID, // 'Wow-Space-Id'
AGGREGATE_ID: 'Command-Aggregate-Id',
AGGREGATE_VERSION: 'Command-Aggregate-Version',
WAIT_PREFIX: 'Command-Wait-',
WAIT_TIME_OUT: 'Command-Wait-Timeout',
WAIT_STAGE: 'Command-Wait-Stage',
WAIT_CONTEXT: 'Command-Wait-Context',
WAIT_PROCESSOR: 'Command-Wait-Processor',
WAIT_FUNCTION: 'Command-Wait-Function',
WAIT_TAIL_PREFIX: 'Command-Wait-Tail-',
WAIT_TAIL_STAGE: 'Command-Wait-Tail-Stage',
WAIT_TAIL_CONTEXT: 'Command-Wait-Tail-Context',
WAIT_TAIL_PROCESSOR: 'Command-Wait-Tail-Processor',
WAIT_TAIL_FUNCTION: 'Command-Wait-Tail-Function',
REQUEST_ID: 'Command-Request-Id',
LOCAL_FIRST: 'Command-Local-First',
COMMAND_AGGREGATE_CONTEXT: 'Command-Aggregate-Context',
COMMAND_AGGREGATE_NAME: 'Command-Aggregate-Name',
COMMAND_TYPE: 'Command-Type',
COMMAND_HEADER_X_PREFIX: 'Command-Header-',
} as const);typescript/wow-client/src/command/commandHeaders.ts:35
WowHeaders
Expand all fields and members
export const WowHeaders = Object.freeze({
WOW_HEADERS_PREFIX: 'Wow-',
SPACE_ID: 'Wow-Space-Id',
ERROR_CODE: 'Wow-Error-Code',
} as const);typescript/wow-client/src/types/headers.ts:33
CommandStageName
export type CommandStageName = CommandStage | `${CommandStage}`;typescript/wow-client/src/command/commandRequest.ts:21
CommandRequestHeaders
Expand all fields and members
export interface CommandRequestHeaders extends RequestHeaders {
[CommandHeaders.TENANT_ID]?: string;
[CommandHeaders.OWNER_ID]?: string;
[CommandHeaders.SPACE_ID]?: string;
[CommandHeaders.AGGREGATE_ID]?: string;
[CommandHeaders.AGGREGATE_VERSION]?: `${number}`;
[CommandHeaders.WAIT_TIME_OUT]?: `${number}`;
[CommandHeaders.WAIT_STAGE]?: CommandStageName;
[CommandHeaders.WAIT_CONTEXT]?: string;
[CommandHeaders.WAIT_PROCESSOR]?: string;
[CommandHeaders.WAIT_FUNCTION]?: string;
[CommandHeaders.WAIT_TAIL_STAGE]?: CommandStageName;
[CommandHeaders.WAIT_TAIL_CONTEXT]?: string;
[CommandHeaders.WAIT_TAIL_PROCESSOR]?: string;
[CommandHeaders.WAIT_TAIL_FUNCTION]?: string;
[CommandHeaders.REQUEST_ID]?: string;
[CommandHeaders.LOCAL_FIRST]?: 'true' | 'false';
[CommandHeaders.COMMAND_AGGREGATE_CONTEXT]?: string;
[CommandHeaders.COMMAND_AGGREGATE_NAME]?: string;
[CommandHeaders.COMMAND_TYPE]?: string;
}typescript/wow-client/src/command/commandRequest.ts:40
CommandHeaderOptions
export interface CommandHeaderOptions {
tenantId?: string;
ownerId?: string;
spaceId?: string;
aggregateId?: string;
aggregateVersion?: number;
requestId?: string;
localFirst?: boolean;
}typescript/wow-client/src/command/commandRequest.ts:82
WaitFunction
export interface WaitFunction {
context?: string;
processor?: string;
function?: string;
}typescript/wow-client/src/command/commandRequest.ts:95
WaitStageOptions
export interface WaitStageOptions extends WaitFunction {
stage?: CommandStageName;
timeoutMs?: number;
tail?: never;
}typescript/wow-client/src/command/commandRequest.ts:102
WaitChainOptions
export interface WaitChainOptions extends WaitFunction {
stage: CommandStage.SAGA_HANDLED | 'SAGA_HANDLED';
tail: WaitFunction & { stage: CommandStageName };
timeoutMs?: number;
}typescript/wow-client/src/command/commandRequest.ts:114
WaitStrategyOptions
export type WaitStrategyOptions = WaitStageOptions | WaitChainOptions;typescript/wow-client/src/command/commandRequest.ts:122
commandHeaders
export function commandHeaders(
options: CommandHeaderOptions,
): CommandRequestHeaders;typescript/wow-client/src/command/commandRequest.ts:168
waitStrategy
export function waitStrategy(
options: WaitStrategyOptions,
): CommandRequestHeaders;typescript/wow-client/src/command/commandRequest.ts:213
CommandUrlParams
export interface CommandUrlParams extends Omit<UrlParams, 'path' | 'query'> {
path?: UrlPathParams;
}typescript/wow-client/src/command/commandRequest.ts:245
CommandRequest
export interface CommandRequest<
C extends object = object,
> extends ParameterRequest<CommandBody<C>> {
urlParams?: CommandUrlParams;
headers?: CommandRequestHeaders;
body?: CommandBody<C>;
}typescript/wow-client/src/command/commandRequest.ts:253
WaitSignal
export interface WaitSignal
extends
Identifier,
WaitCommandIdCapable,
CommandId,
AggregateIdCapable,
NullableAggregateVersionCapable,
ErrorInfo,
SignalTimeCapable,
CommandResultCapable,
FunctionInfoCapable {}typescript/wow-client/src/command/commandResult.ts:52
CommandResult
export interface CommandResult
extends
Identifier,
WaitCommandIdCapable,
CommandStageCapable,
NamedBoundedContext,
AggregateNameCapable,
AggregateId,
CommandId,
RequestId,
ErrorInfo,
FunctionInfoCapable,
CommandResultCapable,
SignalTimeCapable,
NullableAggregateVersionCapable {}typescript/wow-client/src/command/commandResult.ts:74
CommandResultArray
export type CommandResultArray = CommandResult[];typescript/wow-client/src/command/commandResult.ts:90
CommandResultEventStream
export type CommandResultEventStream = ReadableStream<
JsonServerSentEvent<CommandResult>
>;typescript/wow-client/src/command/commandResult.ts:107
CommandId
export interface CommandId {
commandId: string;
}typescript/wow-client/src/command/types.ts:27
WaitCommandIdCapable
export interface WaitCommandIdCapable {
waitCommandId: string;
}typescript/wow-client/src/command/types.ts:36
RequestId
export interface RequestId {
requestId: string;
}typescript/wow-client/src/command/types.ts:45
CommandStage
export enum CommandStage {
SENT = 'SENT',
PROCESSED = 'PROCESSED',
SNAPSHOT = 'SNAPSHOT',
PROJECTED = 'PROJECTED',
EVENT_HANDLED = 'EVENT_HANDLED',
SAGA_HANDLED = 'SAGA_HANDLED',
}typescript/wow-client/src/command/types.ts:54
CommandStageCapable
export interface CommandStageCapable {
stage: CommandStage;
}typescript/wow-client/src/command/types.ts:91
CommandResultCapable
export interface CommandResultCapable {
result: Record<string, any>;
}typescript/wow-client/src/command/types.ts:100
SignalTimeCapable
export interface SignalTimeCapable {
signalTime: number;
}typescript/wow-client/src/command/types.ts:109
NullableAggregateVersionCapable
export interface NullableAggregateVersionCapable {
aggregateVersion?: number;
}typescript/wow-client/src/command/types.ts:118
CompensationTarget
export interface CompensationTarget
extends PartialBy<Identifier, 'id'>, FunctionInfoCapable {}typescript/wow-client/src/command/types.ts:135
DeleteAggregate
export interface DeleteAggregate {}typescript/wow-client/src/command/types.ts:146
CommandBody
export type CommandBody<C> = RemoveReadonlyFields<C>;typescript/wow-client/src/command/types.ts:148
DeleteAggregateCommand
export type DeleteAggregateCommand = CommandBody<DeleteAggregate>;typescript/wow-client/src/command/types.ts:150
RecoverAggregate
export interface RecoverAggregate {}typescript/wow-client/src/command/types.ts:160
RecoverAggregateCommand
export type RecoverAggregateCommand = CommandBody<RecoverAggregate>;typescript/wow-client/src/command/types.ts:162
ApplyResourceTags
export interface ApplyResourceTags extends ApplyAbacTags {}typescript/wow-client/src/command/types.ts:165
ApplyResourceTagsCommand
export type ApplyResourceTagsCommand = CommandBody<ApplyResourceTags>;typescript/wow-client/src/command/types.ts:167
BatchResult
export interface BatchResult extends ErrorInfo {
after: string;
size: number;
}typescript/wow-client/src/command/types.ts:175
Related topics
Client configuration and metadata · Snapshot queries · Filter expressions and legacy conditions · Projection, sorting and pagination · Cursor queries · Aggregation builders · Events and historical state · Identity and resource attribution