export const APPLICATION_COMMAND_ERROR_CODES = [ 'invalid-command-arguments', 'invalid-command-result', 'command-unavailable', 'command-failed', 'session-details-conflict', 'session-size-limit', 'session-revision-conflict', 'SESSION_RUNTIME_WRITER_LOST', 'csl-file-too-large', 'csl-invalid-xml', 'csl-unsupported-doctype', 'csl-unsupported-style', 'csl-missing-metadata', 'csl-dependent-style', 'csl-undefined-macro', 'csl-missing-sections' ] as const export type ApplicationCommandErrorCode = (typeof APPLICATION_COMMAND_ERROR_CODES)[number] export type RuntimeCodec = Readonly<{ parse: (value: unknown) => Value }> export const validationCodec = (codec: RuntimeCodec): RuntimeCodec => Object.freeze({ parse: (value): Value => { codec.parse(value) return value as Value } }) export type ApplicationCommandContract = Readonly<{ args: RuntimeCodec result: RuntimeCodec }> // Domain modules compose runtime-validated command contracts with this helper so arg/result codecs // stay colocated with the shared domain types they parse. export const defineApplicationCommandContract = ( args: ApplicationCommandContract['args'], result: ApplicationCommandContract['result'] ): ApplicationCommandContract => Object.freeze({ args, result }) export type ApplicationCommandErrorEnvelope = Readonly<{ code: ApplicationCommandErrorCode message: string parameters?: Readonly<{ macro: string }> }> export type ApplicationCommandOutcome = | Readonly<{ ok: true; result: Result }> | Readonly<{ ok: false; error: ApplicationCommandErrorEnvelope }> const errorCodes = new Set(APPLICATION_COMMAND_ERROR_CODES) export const isApplicationCommandErrorCode = ( value: unknown ): value is ApplicationCommandErrorCode => typeof value === 'string' && errorCodes.has(value) export class ApplicationCommandError extends Error { constructor( readonly code: ApplicationCommandErrorCode, message: string, readonly parameters?: Readonly<{ macro: string }> ) { super(message) this.name = 'ApplicationCommandError' } } export const toApplicationCommandErrorEnvelope = ( error: unknown ): ApplicationCommandErrorEnvelope => Object.freeze( error instanceof ApplicationCommandError ? { code: error.code, message: error.message, ...(error.code === 'csl-undefined-macro' && error.parameters ? { parameters: { macro: error.parameters.macro } } : {}) } : { code: 'command-failed' as const, message: error instanceof Error ? error.message : String(error) } ) const invalidOutcome = (): ApplicationCommandError => new ApplicationCommandError( 'invalid-command-result', 'Application command returned an invalid response.' ) export const parseApplicationCommandError = ( error: unknown ): ApplicationCommandError | undefined => { if ( error != null && typeof error === 'object' && 'code' in error && isApplicationCommandErrorCode(error.code) && 'message' in error && typeof error.message === 'string' ) { const parameters = 'parameters' in error ? error.parameters : undefined if (error.code === 'csl-undefined-macro') { if ( !parameters || typeof parameters !== 'object' || !('macro' in parameters) || typeof parameters.macro !== 'string' || Object.keys(parameters).length !== 1 ) return undefined return new ApplicationCommandError(error.code, error.message, { macro: parameters.macro }) } if (parameters !== undefined) return undefined return new ApplicationCommandError(error.code, error.message) } return undefined } export const unwrapApplicationCommandOutcome = (value: unknown): Result => { if (!value || typeof value !== 'object' || !('ok' in value)) throw invalidOutcome() if (value.ok === true && 'result' in value) return value.result as Result if (value.ok === false && 'error' in value) { throw parseApplicationCommandError(value.error) ?? invalidOutcome() } throw invalidOutcome() }