Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/pipes/fixtures/connected-account-dto.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
"refresh_token": "ghr_xxxxxxxxxxxxxxxxxxxx",
"expires_at": "2025-12-31T23:59:59.000Z",
"scopes": ["repo", "user:email"],
"state": "connected"
}
26 changes: 26 additions & 0 deletions src/pipes/fixtures/create-data-integration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"provider": "github",
"description": "Production GitHub app",
"enabled": true,
"scopes": ["repo", "read:org"],
"credentials": {
"type": "custom",
"client_id": "Iv1.abc123",
"client_secret": "secret_…"
},
"custom_provider": {
"name": "My OAuth App",
"authorization_url": "https://provider.example.com/oauth/authorize",
"token_url": "https://provider.example.com/oauth/token",
"refresh_token_url": "https://provider.example.com/oauth/token",
"pkce_enabled": true,
"request_scope_separator": " ",
"scopes_required": false,
"client_secret_required": true,
"additional_authorization_parameters": {
"prompt": "consent"
},
"token_body_content_type": "application/x-www-form-urlencoded",
"authenticate_via": "request_body"
}
}
15 changes: 15 additions & 0 deletions src/pipes/fixtures/custom-provider-definition.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "My OAuth App",
"authorization_url": "https://provider.example.com/oauth/authorize",
"token_url": "https://provider.example.com/oauth/token",
"refresh_token_url": "https://provider.example.com/oauth/token",
"pkce_enabled": true,
"request_scope_separator": " ",
"scopes_required": false,
"client_secret_required": true,
"additional_authorization_parameters": {
"prompt": "consent"
},
"token_body_content_type": "application/x-www-form-urlencoded",
"authenticate_via": "request_body"
}
5 changes: 5 additions & 0 deletions src/pipes/fixtures/data-integration-credential.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "custom",
"client_id": "Iv1.abc123",
"redacted_client_secret": "6789"
}
5 changes: 5 additions & 0 deletions src/pipes/fixtures/data-integration-credentials-dto.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "custom",
"client_id": "Iv1.abc123",
"client_secret": "secret_…"
}
15 changes: 15 additions & 0 deletions src/pipes/fixtures/data-integration-custom-provider.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "My OAuth App",
"authorization_url": "https://provider.example.com/oauth/authorize",
"token_url": "https://provider.example.com/oauth/token",
"refresh_token_url": "https://provider.example.com/oauth/token",
"pkce_enabled": true,
"request_scope_separator": " ",
"scopes_required": false,
"client_secret_required": true,
"additional_authorization_parameters": {
"prompt": "consent"
},
"token_body_content_type": "application/x-www-form-urlencoded",
"authenticate_via": "request_body"
}
33 changes: 33 additions & 0 deletions src/pipes/fixtures/data-integration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"object": "data_integration",
"id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT",
"slug": "github",
"integration_type": "github",
"description": "Production GitHub app",
"enabled": true,
"state": "valid",
"scopes": ["repo", "read:org"],
"redirect_uri": "https://api.workos.com/data-integrations/github/dik_01EHZNVPK3SFK441A1RGBFSHRT/callback",
"credentials": {
"type": "custom",
"client_id": "Iv1.abc123",
"redacted_client_secret": "6789"
},
"custom_provider": {
"name": "My OAuth App",
"authorization_url": "https://provider.example.com/oauth/authorize",
"token_url": "https://provider.example.com/oauth/token",
"refresh_token_url": "https://provider.example.com/oauth/token",
"pkce_enabled": true,
"request_scope_separator": " ",
"scopes_required": false,
"client_secret_required": true,
"additional_authorization_parameters": {
"prompt": "consent"
},
"token_body_content_type": "application/x-www-form-urlencoded",
"authenticate_via": "request_body"
},
"created_at": "2026-01-15T12:00:00.000Z",
"updated_at": "2026-01-15T12:00:00.000Z"
}
41 changes: 41 additions & 0 deletions src/pipes/fixtures/list-data-integration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"data": [
{
"object": "data_integration",
"id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT",
"slug": "github",
"integration_type": "github",
"description": "Production GitHub app",
"enabled": true,
"state": "valid",
"scopes": ["repo", "read:org"],
"redirect_uri": "https://api.workos.com/data-integrations/github/dik_01EHZNVPK3SFK441A1RGBFSHRT/callback",
"credentials": {
"type": "custom",
"client_id": "Iv1.abc123",
"redacted_client_secret": "6789"
},
"custom_provider": {
"name": "My OAuth App",
"authorization_url": "https://provider.example.com/oauth/authorize",
"token_url": "https://provider.example.com/oauth/token",
"refresh_token_url": "https://provider.example.com/oauth/token",
"pkce_enabled": true,
"request_scope_separator": " ",
"scopes_required": false,
"client_secret_required": true,
"additional_authorization_parameters": {
"prompt": "consent"
},
"token_body_content_type": "application/x-www-form-urlencoded",
"authenticate_via": "request_body"
},
"created_at": "2026-01-15T12:00:00.000Z",
"updated_at": "2026-01-15T12:00:00.000Z"
}
],
"list_metadata": {
"before": null,
"after": null
}
}
15 changes: 15 additions & 0 deletions src/pipes/fixtures/update-custom-provider-definition.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "My OAuth App",
"authorization_url": "https://provider.example.com/oauth/authorize",
"token_url": "https://provider.example.com/oauth/token",
"refresh_token_url": "https://provider.example.com/oauth/token",
"pkce_enabled": true,
"request_scope_separator": " ",
"scopes_required": false,
"client_secret_required": true,
"additional_authorization_parameters": {
"prompt": "consent"
},
"token_body_content_type": "application/x-www-form-urlencoded",
"authenticate_via": "request_body"
}
25 changes: 25 additions & 0 deletions src/pipes/fixtures/update-data-integration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"description": "Production GitHub app",
"enabled": true,
"scopes": ["repo", "read:org"],
"credentials": {
"type": "custom",
"client_id": "Iv1.abc123",
"client_secret": "secret_…"
},
"custom_provider": {
"name": "My OAuth App",
"authorization_url": "https://provider.example.com/oauth/authorize",
"token_url": "https://provider.example.com/oauth/token",
"refresh_token_url": "https://provider.example.com/oauth/token",
"pkce_enabled": true,
"request_scope_separator": " ",
"scopes_required": false,
"client_secret_required": true,
"additional_authorization_parameters": {
"prompt": "consent"
},
"token_body_content_type": "application/x-www-form-urlencoded",
"authenticate_via": "request_body"
}
}
24 changes: 24 additions & 0 deletions src/pipes/interfaces/connected-account-dto.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// This file is auto-generated by oagen. Do not edit.

import type { ConnectedAccountState } from './connected-account-state.interface';

export interface ConnectedAccountDto {
/** The OAuth access token for the connected account. */
accessToken?: string;
/** The OAuth refresh token for the connected account. */
refreshToken?: string;
/** The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. */
expiresAt?: Date;
/** The OAuth scopes granted for this connection. */
scopes?: string[];
/** Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. */
state?: ConnectedAccountState;
}

export interface ConnectedAccountDtoResponse {
access_token?: string;
refresh_token?: string;
expires_at?: string;
scopes?: string[];
state?: ConnectedAccountState;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
export const ConnectedAccountState = {
Connected: 'connected',
NeedsReauthorization: 'needs_reauthorization',
Disconnected: 'disconnected',
} as const;
Comment on lines 3 to 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Restore disconnected state
ConnectedAccount still documents disconnected as a possible API state, and the existing connected-account serializers type response.state as ConnectedAccountState. Removing Disconnected from this exported const makes valid disconnected responses unrepresentable in TypeScript and breaks callers using ConnectedAccountState.Disconnected for delete/list states.

Artifacts

Repro: TypeScript source that imports ConnectedAccountState.Disconnected from the real SDK interface

  • Contains supporting evidence from the run (text/typescript; charset=utf-8).

Repro: tsc output showing ConnectedAccountState.Disconnected is not exported

  • Keeps the command output available without making the summary code-heavy.

Repro: runtime serializer source showing disconnected payload pass-through

  • Contains supporting evidence from the run (text/typescript; charset=utf-8).

Repro: runtime output showing serializer preserves disconnected state

  • Keeps the command output available without making the summary code-heavy.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/pipes/interfaces/connected-account-state.interface.ts
Line: 3-6

Comment:
**Restore disconnected state**
`ConnectedAccount` still documents `disconnected` as a possible API state, and the existing connected-account serializers type `response.state` as `ConnectedAccountState`. Removing `Disconnected` from this exported const makes valid disconnected responses unrepresentable in TypeScript and breaks callers using `ConnectedAccountState.Disconnected` for delete/list states.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think disconnected should be restored, because it's not a state the API actually returns—but I could be completely wrong.

The enum dropped to two values because this is the first PR to pull in the Pipes connected-account schemas, and those (ConnectedAccountDto and the create/update-connected-account options) only define connected / needs_reauthorization. disconnected only ever lived in the ConnectedAccount response schema (added in https://github.com/workos/workos/pull/50448), but it was never wired through the backend:

  • The domain entity is z.enum(['connected', 'needs_reauthorization'])
  • On disconnect we delete the installation row rather than setting state: 'disconnected'. The pipes.connected_account.disconnected webhook is the event; there's no lingering object carrying that state

So no response has ever come back with state: 'disconnected'. Adding it back to the SDK type would just re-introduce a value the API doesn't emit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a convincing clarification — if the domain entity is z.enum(['connected', 'needs_reauthorization']) and disconnect is modeled as a row deletion + webhook event rather than a state transition, then Disconnected was never a valid SDK value to expose. The removal is correct.

Happy to dismiss this comment. No rule needed here.


export type ConnectedAccountState =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// This file is auto-generated by oagen. Do not edit.

import type { DataIntegrationCredentialsDto } from './data-integration-credentials-dto.interface';
import type { CustomProviderDefinition } from './custom-provider-definition.interface';

export interface CreateDataIntegrationOptions {
/** The provider to create a Data Integration for. For a built-in provider use its slug (e.g. `github`, `slack`). For a custom provider, this is the new provider slug and `custom_provider` must be supplied. A custom provider slug cannot shadow an existing global provider slug. */
provider: string;
/** An optional description of the Data Integration. */
description?: string | null;
/** Whether the Data Integration is enabled. Defaults to `false`. */
enabled?: boolean;
/** The OAuth scopes to request for the Data Integration. Defaults to the provider's configured scopes when omitted. */
scopes?: string[] | null;
/** The credentials to configure for the Data Integration. Required for both built-in and custom providers. */
credentials?: DataIntegrationCredentialsDto;
/** The OAuth definition for a custom provider. Supply this to define a custom provider; omit it to create an integration for a built-in provider. */
customProvider?: CustomProviderDefinition;
}
34 changes: 34 additions & 0 deletions src/pipes/interfaces/create-data-integration.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This file is auto-generated by oagen. Do not edit.

import type {
DataIntegrationCredentialsDto,
DataIntegrationCredentialsDtoResponse,
} from './data-integration-credentials-dto.interface';
import type {
CustomProviderDefinition,
CustomProviderDefinitionResponse,
} from './custom-provider-definition.interface';

export interface CreateDataIntegration {
/** The provider to create a Data Integration for. For a built-in provider use its slug (e.g. `github`, `slack`). For a custom provider, this is the new provider slug and `custom_provider` must be supplied. A custom provider slug cannot shadow an existing global provider slug. */
provider: string;
/** An optional description of the Data Integration. */
description?: string | null;
/** Whether the Data Integration is enabled. Defaults to `false`. */
enabled?: boolean;
/** The OAuth scopes to request for the Data Integration. Defaults to the provider's configured scopes when omitted. */
scopes?: string[] | null;
/** The credentials to configure for the Data Integration. Required for both built-in and custom providers. */
credentials?: DataIntegrationCredentialsDto;
/** The OAuth definition for a custom provider. Supply this to define a custom provider; omit it to create an integration for a built-in provider. */
customProvider?: CustomProviderDefinition;
}

export interface CreateDataIntegrationResponse {
provider: string;
description?: string | null;
enabled?: boolean;
scopes?: string[] | null;
credentials?: DataIntegrationCredentialsDtoResponse;
custom_provider?: CustomProviderDefinitionResponse;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// This file is auto-generated by oagen. Do not edit.

import type { ConnectedAccountState } from './connected-account-state.interface';

export interface CreateUserConnectedAccountOptions {
/** A [User](https://workos.com/docs/reference/authkit/user) identifier. */
userId: string;
/** The slug identifier of the provider (e.g., `github`, `slack`, `notion`). */
slug: string;
/** An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. */
organizationId?: string;
/** The OAuth access token for the connected account. */
accessToken?: string;
/** The OAuth refresh token for the connected account. */
refreshToken?: string;
/** The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. */
expiresAt?: Date;
/** The OAuth scopes granted for this connection. */
scopes?: string[];
/** Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. */
state?: ConnectedAccountState;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This file is auto-generated by oagen. Do not edit.

export const CustomProviderDefinitionAuthenticateVia = {
RequestBody: 'request_body',
BasicAuthHeader: 'basic_auth_header',
} as const;

export type CustomProviderDefinitionAuthenticateVia =
(typeof CustomProviderDefinitionAuthenticateVia)[keyof typeof CustomProviderDefinitionAuthenticateVia];
42 changes: 42 additions & 0 deletions src/pipes/interfaces/custom-provider-definition.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// This file is auto-generated by oagen. Do not edit.

import type { CustomProviderDefinitionAuthenticateVia } from './custom-provider-definition-authenticate-via.interface';

export interface CustomProviderDefinition {
/** A descriptive name for the custom provider. */
name: string;
/** The provider's OAuth authorization endpoint. */
authorizationUrl: string;
/** The provider's OAuth token endpoint. */
tokenUrl: string;
/** The endpoint used to refresh tokens, if different from the token endpoint. */
refreshTokenUrl?: string | null;
/** Whether PKCE is used during the authorization code flow. Defaults to `true`. */
pkceEnabled?: boolean;
/** The separator used to join requested scopes. Defaults to a space. */
requestScopeSeparator?: string;
/** Whether at least one scope must be selected when connecting an account. Defaults to `false`. */
scopesRequired?: boolean;
/** Whether a client secret is required for this provider. Defaults to `true`. */
clientSecretRequired?: boolean;
/** Additional static query parameters appended to the authorization request. */
additionalAuthorizationParameters?: Record<string, string>;
/** The Content-Type used when exchanging the token request. */
tokenBodyContentType?: string;
/** How client credentials are sent when exchanging authorization codes and refreshing tokens. */
authenticateVia?: CustomProviderDefinitionAuthenticateVia;
}

export interface CustomProviderDefinitionResponse {
name: string;
authorization_url: string;
token_url: string;
refresh_token_url?: string | null;
pkce_enabled?: boolean;
request_scope_separator?: string;
scopes_required?: boolean;
client_secret_required?: boolean;
additional_authorization_parameters?: Record<string, string>;
token_body_content_type?: string;
authenticate_via?: CustomProviderDefinitionAuthenticateVia;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This file is auto-generated by oagen. Do not edit.

export const DataIntegrationCredentialType = {
Custom: 'custom',
Organization: 'organization',
} as const;

export type DataIntegrationCredentialType =
(typeof DataIntegrationCredentialType)[keyof typeof DataIntegrationCredentialType];
19 changes: 19 additions & 0 deletions src/pipes/interfaces/data-integration-credential.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// This file is auto-generated by oagen. Do not edit.

import type { DataIntegrationCredentialType } from './data-integration-credential-type.interface';

/** The credentials configured for the Data Integration. */
export interface DataIntegrationCredential {
/** The credentials type. `custom` uses your own OAuth app credentials; `organization` has each organization supply its own credentials (so `client_id`/`redacted_client_secret` are null on the integration itself). */
type: DataIntegrationCredentialType;
/** The OAuth client ID configured for the provider app. Null for `organization` credentials. */
clientId: string | null;
/** The last four characters of the OAuth client secret. The full secret is never returned. Null for `organization` credentials. */
redactedClientSecret: string | null;
}

export interface DataIntegrationCredentialResponse {
type: DataIntegrationCredentialType;
client_id: string | null;
redacted_client_secret: string | null;
}
Loading