101 lines
3.0 KiB
TypeScript
101 lines
3.0 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import type {
|
|
HarnessAdapter,
|
|
HarnessErrorCode,
|
|
HarnessErrorDto,
|
|
HarnessSelection,
|
|
} from '@mosaicstack/types';
|
|
|
|
/**
|
|
* A typed harness operation failure that carries a fully-formed, browser-safe
|
|
* {@link HarnessErrorDto}. The DTO's `selection` is always the exact requested
|
|
* tuple — there is no field through which a substituted "effective" selection
|
|
* could ever be reported.
|
|
*/
|
|
export class HarnessOperationError extends Error {
|
|
readonly code: HarnessErrorCode;
|
|
readonly dto: HarnessErrorDto;
|
|
|
|
constructor(dto: HarnessErrorDto) {
|
|
super(dto.message);
|
|
this.name = 'HarnessOperationError';
|
|
this.code = dto.code;
|
|
this.dto = dto;
|
|
}
|
|
}
|
|
|
|
/** Build a {@link HarnessOperationError} that echoes the requested selection unchanged. */
|
|
export function operationError(
|
|
code: HarnessErrorCode,
|
|
message: string,
|
|
selection: HarnessSelection,
|
|
correlationId: string,
|
|
retryable = false,
|
|
): HarnessOperationError {
|
|
return new HarnessOperationError({ code, message, retryable, correlationId, selection });
|
|
}
|
|
|
|
/** Raised when an unknown harness id is looked up. Discriminated by `code`. */
|
|
export class HarnessAdapterUnavailableError extends Error {
|
|
readonly code = 'adapter_unavailable' as const satisfies HarnessErrorCode;
|
|
|
|
constructor(readonly harnessId: string) {
|
|
super(`No harness adapter is registered for id "${harnessId}".`);
|
|
this.name = 'HarnessAdapterUnavailableError';
|
|
}
|
|
}
|
|
|
|
export type HarnessRegistrationFailure = 'blank_id' | 'duplicate_id';
|
|
|
|
/** Raised when an adapter cannot be registered (blank or duplicate id). */
|
|
export class HarnessRegistrationError extends Error {
|
|
constructor(
|
|
readonly reason: HarnessRegistrationFailure,
|
|
readonly harnessId: string,
|
|
) {
|
|
super(
|
|
reason === 'blank_id'
|
|
? 'A harness adapter id must be a non-empty string.'
|
|
: `A harness adapter is already registered for id "${harnessId}".`,
|
|
);
|
|
this.name = 'HarnessRegistrationError';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Harness-neutral adapter registry. Adapters are keyed by their harness id.
|
|
* Registration rejects blank and duplicate ids; lookup of an unknown id fails
|
|
* with {@link HarnessAdapterUnavailableError} (`adapter_unavailable`).
|
|
*/
|
|
@Injectable()
|
|
export class HarnessRegistry {
|
|
private readonly adapters = new Map<string, HarnessAdapter>();
|
|
|
|
register(adapter: HarnessAdapter): void {
|
|
const id = adapter.id;
|
|
if (typeof id !== 'string' || id.trim().length === 0) {
|
|
throw new HarnessRegistrationError('blank_id', id ?? '');
|
|
}
|
|
if (this.adapters.has(id)) {
|
|
throw new HarnessRegistrationError('duplicate_id', id);
|
|
}
|
|
this.adapters.set(id, adapter);
|
|
}
|
|
|
|
get(harnessId: string): HarnessAdapter {
|
|
const adapter = this.adapters.get(harnessId);
|
|
if (!adapter) {
|
|
throw new HarnessAdapterUnavailableError(harnessId);
|
|
}
|
|
return adapter;
|
|
}
|
|
|
|
has(harnessId: string): boolean {
|
|
return this.adapters.has(harnessId);
|
|
}
|
|
|
|
list(): readonly HarnessAdapter[] {
|
|
return [...this.adapters.values()];
|
|
}
|
|
}
|