import { Schema } from "effect" export abstract class NamedError extends Error { abstract schema(): Schema.Top abstract toObject(): { name: string; data: unknown } static hasName(error: unknown, name: string): boolean { return ( typeof error === "object" && error !== null && "name" in error && (error as Record).name === name ) } static create( name: Name, fields: Fields, ): ReturnType>> static create( name: Name, data: DataSchema, ): ReturnType> static create(name: Name, data: Schema.Top | Schema.Struct.Fields) { return NamedError.createSchemaClass(name, Schema.isSchema(data) ? data : Schema.Struct(data)) } private static createSchemaClass(name: Name, data: DataSchema) { const schema = Schema.Struct({ name: Schema.Literal(name), data, }).annotate({ identifier: name }) type Data = Schema.Schema.Type const result = class extends NamedError { public static readonly Schema = schema public static readonly EffectSchema = schema public static readonly tag = name public override readonly name = name constructor( public readonly data: Data, options?: ErrorOptions, ) { super(name, options) this.name = name } static isInstance(input: unknown): input is InstanceType { return NamedError.hasName(input, name) } schema() { return schema } toObject() { return { name: name, data: this.data, } } } Object.defineProperty(result, "name", { value: name }) return result } public static readonly Unknown = NamedError.create("UnknownError", { message: Schema.String, }) }