/** * Slack client types. * * Defines the SlackResponse discriminated union that wraps every * Slack API call, plus the SlackClient interface consumers interact with. */ import { z } from "zod"; import type { BaseIntegrationClient } from "../../types.js"; import type { ApiRequestOptions, TraceMetadata } from "../base/types.js"; /** * Zod schema for Slack API error responses. * * Slack returns errors as HTTP 200 with `{ ok: false, error: "..." }`. * For `missing_scope` errors, `needed` and `provided` list the * required vs current OAuth scopes. */ export const SlackErrorSchema = z.object({ ok: z.literal(false), error: z.string(), needed: z.string().optional(), provided: z.string().optional(), }); /** Slack API error response, derived from {@link SlackErrorSchema}. */ export type SlackErrorResponse = z.infer; /** * Zod schema for Slack success envelopes. * * Success responses from Slack include `{ ok: true, ...payload }`. * Callers provide schemas for the payload fields only; the SDK validates * this envelope separately and injects `ok: true` in the returned type. */ export const SlackSuccessEnvelopeSchema = z.object({ ok: z.literal(true), }); /** * Enforce that caller-provided success schemas do not define `ok`. * * The `ok` field belongs to the Slack envelope and is injected by the SDK. */ type SlackSuccessSchemaHasExplicitOkKey = TSchema extends z.ZodObject< infer TShape, z.UnknownKeysParam, z.ZodTypeAny, unknown, unknown > ? "ok" extends keyof TShape ? true : false : false; type SlackSuccessSchema = SlackSuccessSchemaHasExplicitOkKey extends true ? never : TSchema; /** * Schema configuration for Slack API requests. * * Response schema must be an object payload schema (without `ok`). */ export interface SlackApiRequestSchema< TBody, TResponseSchema extends z.AnyZodObject, > { body?: z.ZodSchema; response: SlackSuccessSchema; } /** Slack success response shape with injected discriminant. */ type SlackSuccessResponse> = T & { readonly ok: true; }; /** * Discriminated union of a Slack success response and error response. * * On success (`ok: true`), the type is `T & { ok: true }` — the * validated schema output intersected with a literal `true` discriminant. * On failure (`ok: false`), the type is {@link SlackErrorResponse}. * * Use the `ok` field as a discriminant for type narrowing: * * @example * ```typescript * const result = await ctx.integrations.slack.apiRequest( * { method: "GET", path: "/conversations.list", params: { limit: 100 } }, * { response: ListChannelsSchema }, * ); * * if (!result.ok) { * // TypeScript narrows to SlackErrorResponse * console.error(`Slack error: ${result.error}`); * return; * } * * // TypeScript narrows to T & { ok: true } * result.channels.forEach(ch => console.log(ch.name)); * ``` */ export type SlackResponse> = | SlackSuccessResponse | SlackErrorResponse; /** * Slack client for API interactions. * * Returns {@link SlackResponse} from `apiRequest` — a discriminated * union that encapsulates both Slack success and error responses. This * avoids confusing Zod validation errors when Slack returns `ok: false` * (which is an HTTP 200 with no success-specific fields). * * Note: SlackClient intentionally does not extend `SupportsApiRequest`. * `SupportsApiRequest.apiRequest` returns `Promise`, while * Slack returns `Promise>` by design. * * @example * ```typescript * const result = await ctx.integrations.slack.apiRequest( * { * method: "POST", * path: "/chat.postMessage", * body: { channel: "#alerts", text: "Deployed!" }, * }, * { response: PostMessageResponseSchema }, * ); * * if (!result.ok) { * throw new Error(`Slack API error: ${result.error}`); * } * * console.log(`Message sent: ${result.ts}`); * ``` */ export interface SlackClient extends BaseIntegrationClient { /** * Execute a Slack API request with type-safe response handling. * * Returns a {@link SlackResponse} discriminated union: * - On success: the Zod-validated payload with injected `ok: true` * - On failure: a {@link SlackErrorResponse} with `ok: false` and the Slack error code * * Body validation still throws {@link RestApiValidationError} if the * request body fails its Zod schema. * * @param options - Request configuration (method, path, params, body, headers) * @param schema - Zod schemas for body and response validation (response REQUIRED) * - `response` must be `z.object(...)` * - `response` should define payload fields only (MUST NOT define `ok`) * @param metadata - Optional trace metadata for observability * @returns Discriminated union — check `result.ok` before accessing fields */ apiRequest( options: ApiRequestOptions, schema: SlackApiRequestSchema, metadata?: TraceMetadata, ): Promise>>; }