254 lines · 9.0 KB · TypeScript
RawDownload
// ---------------------------------------------------------------------------// Connections HTTP API — the v2 credential surface.//// A connection IS the credential: owner-scoped (org | user), bound 1:1 to an// integration, resolving its value through a `CredentialProvider`. Identified by// `(owner, integration, name)`. No scope segments, no token-secret-ids, no// identity-override-by-scope — those v1 concepts are gone.// --------------------------------------------------------------------------- import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";import { Predicate, Schema } from "effect"; import {  AuthTemplateSlug,  ConnectionAddress,  ConnectionName,  ConnectionNotFoundError,  CredentialProviderNotRegisteredError,  HealthCheckResult,  HealthCheckSpec,  IntegrationNotFoundError,  IntegrationSlug,  InternalError,  InvalidConnectionInputError,  OAuthClientSlug,  Owner,  ProviderItemId,  ProviderKey,} from "@executor-js/sdk/shared"; // ---------------------------------------------------------------------------// Params — a connection is identified by (owner, integration, name).// --------------------------------------------------------------------------- const ConnectionParams = {  owner: Owner,  integration: IntegrationSlug,  name: ConnectionName,}; // ---------------------------------------------------------------------------// Response schemas — mirrors the SDK's `Connection`.// --------------------------------------------------------------------------- const ConnectionResponse = Schema.Struct({  owner: Owner,  name: ConnectionName,  integration: IntegrationSlug,  template: AuthTemplateSlug,  provider: ProviderKey,  address: ConnectionAddress,  identityLabel: Schema.NullOr(Schema.String),  description: Schema.NullOr(Schema.String),  expiresAt: Schema.NullOr(Schema.Number),  // The OAuth app that minted this connection (its `oauth_client` slug), or null  // for static credentials. Lets the UI map a connection back to its app. Just a  // slug — never a secret.  oauthClient: Schema.NullOr(OAuthClientSlug),  oauthClientOwner: Schema.NullOr(Owner),  oauthScope: Schema.NullOr(Schema.String),  missingOAuthScopes: Schema.Array(Schema.String),  // Last persisted health-check verdict (written by every checkHealth run),  // so the list can show alive/expired at a glance without probing.  lastHealth: Schema.NullOr(HealthCheckResult),}); const ToolResponse = Schema.Struct({  address: Schema.String,  owner: Owner,  integration: IntegrationSlug,  connection: ConnectionName,  name: Schema.String,  pluginId: Schema.String,  description: Schema.String,}); // ---------------------------------------------------------------------------// Payload schemas// --------------------------------------------------------------------------- // A connection picks exactly one origin: a single pasted `value` (sugar for the// `token` input), a `values` map (one per named input, e.g. both of Datadog's// keys), or an external `from` reference.const CommonCreateFields = {  owner: Owner,  name: ConnectionName,  integration: IntegrationSlug,  template: AuthTemplateSlug,  identityLabel: Schema.optional(Schema.NullOr(Schema.String)),  description: Schema.optional(Schema.NullOr(Schema.String)),} as const; // User-curated metadata edits. Absent field = unchanged; null = cleared.const UpdateConnectionPayload = Schema.Struct({  description: Schema.optional(Schema.NullOr(Schema.String)),  identityLabel: Schema.optional(Schema.NullOr(Schema.String)),}); const CreateConnectionPayload = Schema.Struct({  ...CommonCreateFields,  value: Schema.optional(Schema.String),  values: Schema.optional(Schema.Record(Schema.String, Schema.String)),  from: Schema.optional(    Schema.Struct({      provider: ProviderKey,      id: ProviderItemId,    }),  ),}).check(  Schema.makeFilter((payload) =>    [payload.value, payload.values, payload.from].filter(Predicate.isNotUndefined).length === 1      ? undefined      : "Expected exactly one credential origin",  ),); // Validate an in-flight credential WITHOUT saving it (the key-first connect// flow). Same origin shape as create, plus an optional `spec` override so the// editor can preview a candidate against a live key. No `name`: the point is// to derive one from the identity the probe returns.const ValidateConnectionPayload = Schema.Struct({  owner: Owner,  integration: IntegrationSlug,  template: AuthTemplateSlug,  spec: Schema.optional(HealthCheckSpec),  value: Schema.optional(Schema.String),  values: Schema.optional(Schema.Record(Schema.String, Schema.String)),  from: Schema.optional(    Schema.Struct({      provider: ProviderKey,      id: ProviderItemId,    }),  ),}).check(  Schema.makeFilter((payload) =>    [payload.value, payload.values, payload.from].filter(Predicate.isNotUndefined).length === 1      ? undefined      : "Expected exactly one credential origin",  ),); // ---------------------------------------------------------------------------// Query — optional list filters.// --------------------------------------------------------------------------- const ListConnectionsQuery = Schema.Struct({  integration: Schema.optional(IntegrationSlug),  owner: Schema.optional(Owner),}); // Freshness window for checkHealth: return the persisted verdict when younger// than this many ms; probe otherwise. Bounded to a day: a bigger window is a// caller bug, not a use case.const CheckHealthQuery = Schema.Struct({  ifStaleMs: Schema.optional(    Schema.FiniteFromString.check(Schema.isBetween({ minimum: 0, maximum: 86_400_000 })),  ),}); // ---------------------------------------------------------------------------// Error schemas with HTTP status annotations// --------------------------------------------------------------------------- const ConnectionNotFound = ConnectionNotFoundError.annotate({  httpApiStatus: 404,});const IntegrationNotFound = IntegrationNotFoundError.annotate({  httpApiStatus: 404,});const CredentialProviderNotRegistered = CredentialProviderNotRegisteredError.annotate({  httpApiStatus: 409,});const InvalidConnectionInput = InvalidConnectionInputError.annotate({  httpApiStatus: 400,}); // ---------------------------------------------------------------------------// Group// --------------------------------------------------------------------------- export const ConnectionsApi = HttpApiGroup.make("connections")  .add(    HttpApiEndpoint.get("list", "/connections", {      query: ListConnectionsQuery,      success: Schema.Array(ConnectionResponse),      error: InternalError,    }),  )  .add(    HttpApiEndpoint.post("create", "/connections", {      payload: CreateConnectionPayload,      success: ConnectionResponse,      error: [        InternalError,        IntegrationNotFound,        CredentialProviderNotRegistered,        InvalidConnectionInput,      ],    }),  )  .add(    HttpApiEndpoint.get("get", "/connections/:owner/:integration/:name", {      params: ConnectionParams,      success: ConnectionResponse,      error: [InternalError, ConnectionNotFound],    }),  )  .add(    HttpApiEndpoint.patch("update", "/connections/:owner/:integration/:name", {      params: ConnectionParams,      payload: UpdateConnectionPayload,      success: ConnectionResponse,      error: [InternalError, ConnectionNotFound],    }),  )  .add(    HttpApiEndpoint.delete("remove", "/connections/:owner/:integration/:name", {      params: ConnectionParams,      success: Schema.Struct({ removed: Schema.Boolean }),      error: [InternalError, ConnectionNotFound],    }),  )  .add(    HttpApiEndpoint.post("refresh", "/connections/:owner/:integration/:name/refresh", {      params: ConnectionParams,      success: Schema.Array(ToolResponse),      error: [InternalError, ConnectionNotFound, IntegrationNotFound],    }),  )  // Run the integration's declared health check against a SAVED connection: is  // this credential still alive (Google's 7-day dev-token revocation), and whose  // account is it? Returns a classified status + optional identity, never an  // error for an auth wall (that surfaces as `status: "expired"`).  .add(    HttpApiEndpoint.post("checkHealth", "/connections/:owner/:integration/:name/health", {      params: ConnectionParams,      // `ifStaleMs`: return the persisted verdict when younger than this      // instead of probing (the page-load revalidation path). The server owns      // the freshness decision so open tabs can't stampede an upstream.      query: CheckHealthQuery,      success: HealthCheckResult,      error: [InternalError, ConnectionNotFound, IntegrationNotFound],    }),  )  // Run the health check against an IN-FLIGHT credential without saving it (the  // key-first connect flow): confirm the pasted key works and surface the  // identity the UI derives a connection name from before anything persists.  .add(    HttpApiEndpoint.post("validate", "/connections/validate", {      payload: ValidateConnectionPayload,      success: HealthCheckResult,      error: [InternalError, IntegrationNotFound],    }),  );