import type { z } from 'zod' import { NAMZU } from '../constants/telemetry/index.js' import type { AuthConfig, AuthType, ConnectionType, ConnectorDefinition, ConnectorExecuteResult, ConnectorLifecycle, ConnectorMethod, ConnectorOperationOptions, } from '../types/connector/index.js' import type { ConnectorId } from '../types/ids/index.js' import { SCOPE_ATTRIBUTE } from '../utils/log/types.js' import { type Logger, resolveLogger } from '../utils/logger.js' import { hasManagerValidatedInput } from './execution-contract.js' export abstract class BaseConnector implements ConnectorLifecycle { abstract readonly id: ConnectorId abstract readonly name: string abstract readonly description: string abstract readonly connectionType: ConnectionType /** Omit only when the connector intentionally accepts every auth scheme. */ readonly supportedAuth?: readonly AuthType[] abstract readonly configSchema: z.ZodType abstract readonly methods: ConnectorMethod[] protected log: Logger protected config: TConfig | null = null protected auth: AuthConfig | undefined constructor(log?: Logger) { this.log = resolveLogger(log).child({ [SCOPE_ATTRIBUTE]: 'connector/base', [NAMZU.CONNECTOR_TYPE]: this.constructor.name, }) } abstract connect(config: TConfig, auth?: AuthConfig): Promise abstract disconnect(): Promise abstract healthCheck(options?: ConnectorOperationOptions): Promise abstract execute( method: string, input: unknown, options?: ConnectorOperationOptions, ): Promise toDefinition(): ConnectorDefinition { return { id: this.id, name: this.name, description: this.description, connectionType: this.connectionType, ...(this.supportedAuth ? { supportedAuth: [...this.supportedAuth] } : {}), configSchema: this.configSchema, methods: this.methods, } } protected findMethod(methodName: string): ConnectorMethod | undefined { return this.methods.find((m) => m.name === methodName) } protected requireMethod(methodName: string): ConnectorMethod { const method = this.findMethod(methodName) if (!method) { throw new Error( `Method "${methodName}" not found on connector "${this.id}". ` + `Available: ${this.methods.map((m) => m.name).join(', ')}`, ) } return method } protected async validateInput( method: ConnectorMethod, input: unknown, options?: ConnectorOperationOptions, ): Promise { if (hasManagerValidatedInput(options)) return input const result = await method.inputSchema.safeParseAsync(input) if (!result.success) { const errors = result.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ') throw new Error(`Invalid input for method "${method.name}": ${errors}`) } return result.data } protected measureExecution( fn: () => Promise, ): Promise<{ result: TResult; durationMs: number }> { const start = performance.now() return fn().then((result) => ({ result, durationMs: Math.round(performance.now() - start), })) } }