243 lines · 8.3 KB · TypeScript
RawDownload
import {  EnvironmentId,  type PreviewAutomationHost,  PreviewAutomationOperation,  PreviewAutomationRecordingTransferError,  PreviewAutomationRecordingDesktopUpdateRequiredError,  PreviewAutomationRecordingTooLargeError,  PreviewAutomationRecordingDeadlineExpiredError,  type PreviewAutomationRequest,  type PreviewAutomationResponse,  PreviewTabId,  ThreadId,  TrimmedNonEmptyString,} from "@t3tools/contracts";import * as Schema from "effect/Schema"; export interface PreviewAutomationOperationContext {  readonly requestId: PreviewAutomationRequest["requestId"];  readonly operation: PreviewAutomationRequest["operation"];  readonly environmentId: PreviewAutomationHost["environmentId"];  readonly threadId: PreviewAutomationRequest["threadId"];  readonly tabId: Exclude<PreviewAutomationRequest["tabId"], undefined> | null;} export class PreviewAutomationOverlayTimeoutError extends Schema.TaggedError<PreviewAutomationOverlayTimeoutError>()(  "PreviewAutomationOverlayTimeoutError",  {    requestId: TrimmedNonEmptyString,    environmentId: EnvironmentId,    threadId: ThreadId,    timeoutMs: Schema.Int,  },) {  get responseTag() {    return "PreviewAutomationTimeoutError" as const;  }   override get message(): string {    return `Preview webview for request ${this.requestId} on environment ${this.environmentId} thread ${this.threadId} did not register within ${this.timeoutMs}ms.`;  }} export class PreviewAutomationNavigationTimeoutError extends Schema.TaggedError<PreviewAutomationNavigationTimeoutError>()(  "PreviewAutomationNavigationTimeoutError",  {    requestId: TrimmedNonEmptyString,    environmentId: EnvironmentId,    threadId: ThreadId,    tabId: PreviewTabId,    readiness: Schema.Literals(["domContentLoaded", "load"]),    timeoutMs: Schema.Int,  },) {  get responseTag() {    return "PreviewAutomationTimeoutError" as const;  }   override get message(): string {    return `Preview navigation for request ${this.requestId} on environment ${this.environmentId} thread ${this.threadId} tab ${this.tabId} did not reach ${this.readiness} readiness within ${this.timeoutMs}ms.`;  }} export class PreviewAutomationViewportTimeoutError extends Schema.TaggedError<PreviewAutomationViewportTimeoutError>()(  "PreviewAutomationViewportTimeoutError",  {    requestId: TrimmedNonEmptyString,    environmentId: EnvironmentId,    threadId: ThreadId,    tabId: PreviewTabId,    timeoutMs: Schema.Int,  },) {  get responseTag() {    return "PreviewAutomationTimeoutError" as const;  }   override get message(): string {    return `Preview viewport for request ${this.requestId} on environment ${this.environmentId} thread ${this.threadId} tab ${this.tabId} was not rendered within ${this.timeoutMs}ms.`;  }} export class PreviewAutomationTargetUnavailableError extends Schema.TaggedError<PreviewAutomationTargetUnavailableError>()(  "PreviewAutomationTargetUnavailableError",  {    requestId: TrimmedNonEmptyString,    operation: PreviewAutomationOperation,    environmentId: EnvironmentId,    threadId: ThreadId,    tabId: Schema.NullOr(PreviewTabId),    bridgeAvailable: Schema.Boolean,  },) {  get responseTag() {    return "PreviewAutomationTabNotFoundError" as const;  }   override get message(): string {    return `Preview automation target for ${this.operation} request ${this.requestId} is unavailable on environment ${this.environmentId} thread ${this.threadId} (tab ${this.tabId ?? "unassigned"}, bridge ${this.bridgeAvailable ? "available" : "unavailable"}).`;  }} export class PreviewAutomationRecordingNotActiveError extends Schema.TaggedError<PreviewAutomationRecordingNotActiveError>()(  "PreviewAutomationRecordingNotActiveError",  {    requestId: TrimmedNonEmptyString,    environmentId: EnvironmentId,    threadId: ThreadId,    tabId: Schema.NullOr(PreviewTabId),  },) {  get responseTag() {    return "PreviewAutomationExecutionError" as const;  }   override get message(): string {    return `Preview automation request ${this.requestId} found no active recording for tab ${this.tabId ?? "unassigned"} on environment ${this.environmentId} thread ${this.threadId}.`;  }} export class PreviewAutomationTargetNotEditableHostError extends Schema.TaggedError<PreviewAutomationTargetNotEditableHostError>()(  "PreviewAutomationTargetNotEditableHostError",  {    requestId: TrimmedNonEmptyString,    operation: PreviewAutomationOperation,    environmentId: EnvironmentId,    threadId: ThreadId,    tabId: Schema.NullOr(PreviewTabId),    selectorKind: Schema.optional(Schema.Literals(["focused-element", "locator", "selector"])),    selectorLength: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),  },) {  get responseTag() {    return "PreviewAutomationTargetNotEditableError" as const;  }   override get message(): string {    return `Preview automation ${this.operation} request ${this.requestId} requires an editable target in tab ${this.tabId ?? "unassigned"}.`;  }} const targetNotEditableDiagnostics = (  cause: unknown,): {  readonly selectorKind?: "focused-element" | "locator" | "selector";  readonly selectorLength?: number;} | null => {  if (    typeof cause !== "object" ||    cause === null ||    !("_tag" in cause) ||    cause._tag !== "PreviewAutomationTargetNotEditableError"  ) {    return null;  }  const selectorKind =    "selectorKind" in cause &&    (cause.selectorKind === "focused-element" ||      cause.selectorKind === "locator" ||      cause.selectorKind === "selector")      ? cause.selectorKind      : undefined;  const selectorLength =    "selectorLength" in cause &&    typeof cause.selectorLength === "number" &&    Number.isInteger(cause.selectorLength) &&    cause.selectorLength >= 0      ? cause.selectorLength      : undefined;  return {    ...(selectorKind === undefined ? {} : { selectorKind }),    ...(selectorLength === undefined ? {} : { selectorLength }),  };}; export class PreviewAutomationOperationError extends Schema.TaggedError<PreviewAutomationOperationError>()(  "PreviewAutomationOperationError",  {    requestId: TrimmedNonEmptyString,    operation: PreviewAutomationOperation,    environmentId: EnvironmentId,    threadId: ThreadId,    tabId: Schema.NullOr(PreviewTabId),    cause: Schema.Defect(),  },) {  static fromCause(    input: PreviewAutomationOperationContext & { readonly cause: unknown },  ): PreviewAutomationHostError {    if (isPreviewAutomationHostError(input.cause)) return input.cause;    const diagnostics = targetNotEditableDiagnostics(input.cause);    return diagnostics      ? new PreviewAutomationTargetNotEditableHostError({          requestId: input.requestId,          operation: input.operation,          environmentId: input.environmentId,          threadId: input.threadId,          tabId: input.tabId,          ...diagnostics,        })      : new PreviewAutomationOperationError(input);  }   get responseTag() {    return "PreviewAutomationExecutionError" as const;  }   override get message(): string {    return `Preview automation ${this.operation} request ${this.requestId} failed on environment ${this.environmentId} thread ${this.threadId} (tab ${this.tabId ?? "unassigned"}).`;  }} export const PreviewAutomationHostError = Schema.Union([  PreviewAutomationRecordingTransferError,  PreviewAutomationRecordingDesktopUpdateRequiredError,  PreviewAutomationRecordingTooLargeError,  PreviewAutomationRecordingDeadlineExpiredError,  PreviewAutomationOverlayTimeoutError,  PreviewAutomationNavigationTimeoutError,  PreviewAutomationViewportTimeoutError,  PreviewAutomationTargetUnavailableError,  PreviewAutomationRecordingNotActiveError,  PreviewAutomationTargetNotEditableHostError,  PreviewAutomationOperationError,]);export type PreviewAutomationHostError = typeof PreviewAutomationHostError.Type; const isPreviewAutomationHostError = Schema.is(PreviewAutomationHostError); export function serializePreviewAutomationHostError(  error: PreviewAutomationHostError,): NonNullable<PreviewAutomationResponse["error"]> {  const detail = Object.fromEntries(    Object.entries(error).filter(      ([key]) =>        key !== "_tag" && key !== "cause" && key !== "name" && key !== "message" && key !== "stack",    ),  );  return {    _tag: "responseTag" in error ? error.responseTag : error._tag,    message: error.message,    ...(Object.keys(detail).length === 0 ? {} : { detail }),  };}