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(); 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()]; } }