Skip to content

Plugin SDK Reference

Complete API reference for @tryvienna/sdk — the type-safe foundation for Vienna's plugin, integration, and entity system.

Auto-generated

This reference is generated from the @tryvienna/sdk source code. Regenerate with pnpm --filter @vienna/docs generate:reference.

Installation

@tryvienna/sdk is a workspace package — import it directly in any Vienna plugin or package:

typescript
// Main entry — definitions, URIs, types, registries
import { definePlugin, defineIntegration, defineEntity } from '@tryvienna/sdk';

// React hooks — renderer-only
import { useEntity, useEntities, usePluginQuery } from '@tryvienna/sdk/react';

// Codegen helper — build tooling only
import { createPluginCodegenConfig } from '@tryvienna/sdk/codegen';

Three entry points serve different contexts:

Entry PointUseProcess
@tryvienna/sdkDefinitions, types, URIs, registriesAny
@tryvienna/sdk/reactReact hooks, providers, cache utilsRenderer only
@tryvienna/sdk/codegenGraphQL codegen config factoryBuild tooling

Definition Factories

The three define* factories are the primary API for plugins. Each validates its input, returns an immutable definition object, and provides URI helpers.

defineEntity()

typescript
function defineEntity(config: EntityDefinitionConfig): EntityDefinition
ParameterTypeRequiredDescription
configEntityDefinitionConfigYes

Returns: EntityDefinition

TIP

defineEntity() validates the type against EntityTypeSchema (lowercase alphanumeric + underscore, 1-64 chars) and freezes the returned object.

EntityDefinitionConfig

PropertyTypeRequiredDescription
typestringYesEntity type identifier (validated against EntityTypeSchema)
namestringYesHuman-readable display name
iconPluginIconYesStatic icon asset
uristring[]YesURI segment names (e.g., ['owner', 'repo', 'number'])
descriptionstringNoDescription of what this entity represents
sourceEntitySourceNoWhere this entity comes from
displayEntityDisplayMetadataNoDisplay metadata for automatic styling
cacheEntityCacheConfigNoCache configuration
ui{ drawer?: ComponentType<EntityDrawerProps>; card?: ComponentType<EntityCardProps>; }NoUI components (optional)

EntityDefinition

PropertyTypeRequiredDescription
__brand'EntityDefinition'Yes
typestringYes
namestringYes
iconPluginIconYes
uriSegmentsreadonly string[]Yes
descriptionstringNo
sourceEntitySourceYes
displayEntityDisplayMetadataNo
cacheEntityCacheConfigNo
ui{ readonly drawer?: ComponentType<EntityDrawerProps>; readonly card?: ComponentType<EntityCardProps>; }No

Methods

  • createURI(id: Record<string, string>): string — Build a URI for this entity type
  • parseURI(uri: string): { type: string; id: Record<string, string> } — Parse a URI and extract ID segments

EntityDrawerProps

PropertyTypeRequiredDescription
uristringYes
DrawerContainerComponentType<DrawerContainerProps>YesContainer injected by the host app — wrap drawer content in this.
headerActionsReact.ReactNodeNo
onNavigate(entityUri: string, entityType: string, label?: string) => voidNo
onClose() => voidNo
projectIdstringNo

EntityCardProps

PropertyTypeRequiredDescription
uristringYes
labelstringNo

DrawerContainerProps

Props for a DrawerContainer component injected by the host app. Entity drawers use this to set title, footer, and header actions without depending on the host's internal drawer chrome.

PropertyTypeRequiredDescription
titleReact.ReactNodeNo
headerActionsReact.ReactNodeNo
footerReact.ReactNodeNo
childrenReact.ReactNodeYes

Example

typescript
import { defineEntity } from '@tryvienna/sdk';
import type { EntityDrawerProps, EntityCardProps } from '@tryvienna/sdk';

// Custom entity drawer component
function PRDrawer({ uri, DrawerContainer, onNavigate }: EntityDrawerProps) {
  const { entity, loading } = useEntity(uri);
  if (loading || !entity) return null;

  return (
    <DrawerContainer title={entity.title}>
      <PRDetailView uri={uri} />
    </DrawerContainer>
  );
}

// Custom entity card/chip component (inline preview)
function PRCard({ uri, label }: EntityCardProps) {
  return <span>{label ?? uri}</span>;
}

export const githubPrEntity = defineEntity({
  type: 'github_pr',
  name: 'GitHub Pull Request',
  icon: { svg: '<svg>...</svg>' },
  uri: ['owner', 'repo', 'number'],
  display: {
    emoji: '🔀',
    colors: { bg: '#dafbe1', text: '#116329', border: '#aceebb' },
  },
  cache: { ttl: 30_000, maxSize: 200 },

  // UI overrides — custom rendering when this entity appears in the app
  ui: {
    drawer: PRDrawer,  // Shown when user clicks to expand this entity
    card: PRCard,      // Inline chip/card shown in lists and references
  },
});

// Build a URI
const uri = githubPrEntity.createURI({
  owner: 'anthropics', repo: 'sdk', number: '42',
});
// => '@drift//github_pr/anthropics/sdk/42'

// Parse a URI
const { type, id } = githubPrEntity.parseURI(uri);
// => { type: 'github_pr', id: { owner: 'anthropics', repo: 'sdk', number: '42' } }

defineIntegration()

typescript
function defineIntegration<TClient>(
  config: IntegrationConfig<TClient>,
): IntegrationDefinition<TClient>
ParameterTypeRequiredDescription
configIntegrationConfig&lt;TClient&gt;Yes

Returns: IntegrationDefinition&lt;TClient&gt;

IntegrationConfig<TClient>

PropertyTypeRequiredDescription
idstringYesUnique integration ID (e.g., 'github', 'linear')
namestringYesHuman-readable display name
iconPluginIconYesStatic icon asset
descriptionstringNoDescription of what this integration provides
oauthOAuthConfigNoOAuth configuration for external authentication
credentialsstring[]NoKeys stored in secure storage (e.g., ['api_key', 'personal_access_token'])
createClient(ctx: AuthContext) =&gt; Promise&lt;TClient | null&gt;YesCreate an API client from auth context. Returns null if auth not configured.
schema(builder: SchemaBuilder) =&gt; voidNoOptional GraphQL schema extension. Called with the typed SchemaBuilder.

IntegrationDefinition<_TClient = unknown>

PropertyTypeRequiredDescription
__brand'IntegrationDefinition'Yes
idstringYes
namestringYes
iconPluginIconYes
descriptionstringNo
oauthOAuthConfigNo
credentialsreadonly string[]No
createClient(ctx: AuthContext) =&gt; Promise&lt;_TClient | null&gt;Yes
schema(builder: SchemaBuilder) =&gt; voidNo

Example

typescript
import { defineIntegration } from '@tryvienna/sdk';
import type { SchemaBuilder } from '@tryvienna/sdk';

interface GitHubClient {
  getPR(owner: string, repo: string, number: number): Promise<PRData>;
}

export const githubIntegration = defineIntegration<GitHubClient>({
  id: 'github',
  name: 'GitHub',
  icon: { svg: '<svg>...</svg>' },
  oauth: {
    providers: [{
      providerId: 'github',
      displayName: 'GitHub',
      flow: {
        grantType: 'authorization_code',
        clientId: 'your-client-id',
        authorizationUrl: 'https://github.com/login/oauth/authorize',
        tokenUrl: 'https://github.com/login/oauth/access_token',
        scopes: ['repo', 'read:user'],
      },
    }],
  },
  createClient: async (ctx) => {
    const token = await ctx.oauth?.getAccessToken('github');
    if (!token) return null;
    return new GitHubClient(token);
  },
  schema: (builder) => registerGitHubSchema(builder),
});

definePlugin()

typescript
function definePlugin(config: PluginConfig): PluginDefinition
ParameterTypeRequiredDescription
configPluginConfigYes

Returns: PluginDefinition

PluginConfig

PropertyTypeRequiredDescription
idstringYesUnique plugin identifier (lowercase alphanumeric + underscores)
namestringYesHuman-readable display name
iconPluginIconYesStatic icon asset
descriptionstringNoDescription of what this plugin does
integrationsIntegrationDefinition&lt;any&gt;[]NoIntegration definitions provided by this plugin
entitiesEntityDefinition[]NoEntity definitions provided by this plugin
canvasesPluginCanvasesNoUI canvas contributions
allowedDomainsstring[]NoDomains the plugin is allowed to fetch via hostApi.fetch().
Only exact hostname matches are permitted (e.g. "api.open-meteo.com").

PluginDefinition

PropertyTypeRequiredDescription
__brand'PluginDefinition'Yes
idstringYes
namestringYes
iconPluginIconYes
descriptionstringNo
integrationsreadonly IntegrationDefinition&lt;any&gt;[]Yes
entitiesreadonly EntityDefinition[]Yes
canvasesReadonly&lt;PluginCanvases&gt;Yes
allowedDomainsreadonly string[]Yes

Example

typescript
import { definePlugin } from '@tryvienna/sdk';

export const githubPlugin = definePlugin({
  id: 'github',
  name: 'GitHub',
  icon: { svg: '<svg>...</svg>' },
  integrations: [githubIntegration],
  entities: [githubPrEntity, githubIssueEntity],
  canvases: {
    'nav-sidebar': {
      component: GitHubSidebar,
      label: 'GitHub',
      icon: '🐙',
      priority: 80,
    },
    drawer: {
      component: GitHubDrawer,
      label: 'GitHub Settings',
    },
  },
  allowedDomains: ['api.github.com'],
});

Type Guards

typescript
function isEntityDefinition(value: unknown): value is EntityDefinition
function isIntegrationDefinition(value: unknown): value is IntegrationDefinition
function isPluginDefinition(value: unknown): value is PluginDefinition

Runtime type checks using the __brand discriminator on each definition type.


URI Utilities

Entity URIs follow the pattern @drift//<type>/<segment1>/<segment2>/... with optional labels appended as ?label=<base64>.

@drift//project/abc123
@drift//github_pr/owner/repo/42
@drift//project/abc123?label=TXkgUHJvamVjdA==

DRIFT_URI_SCHEME

typescript
const DRIFT_URI_SCHEME = '@drift//'

The URI scheme prefix. All entity URIs start with this string.

buildEntityURI()

Build an entity URI from type, ID parts, and URI path config.

ts
buildEntityURI('project', { id: 'abc' }, { segments: ['id'] })
// => '@drift//project/abc'
typescript
function buildEntityURI(
  type: string,
  id: Record<string, string>,
  uriPath: EntityURIPath
): string
ParameterTypeRequiredDescription
typestringYes
idRecord&lt;string, string&gt;Yes
uriPathEntityURIPathYes

Returns: string

ts
buildEntityURI('project', { id: 'abc' }, { segments: ['id'] })
// => '@drift//project/abc'

buildEntityURIWithLabel()

Build an entity URI with an optional display label. The label is base64-encoded and appended as a query parameter.

typescript
function buildEntityURIWithLabel(
  type: string,
  id: Record<string, string>,
  uriPath: EntityURIPath,
  label?: string
): string
ParameterTypeRequiredDescription
typestringYes
idRecord&lt;string, string&gt;Yes
uriPathEntityURIPathYes
labelstringNo

Returns: string

parseEntityURI()

Parse an entity URI and extract the type and path segments. If uriPath is provided, segments are mapped to named keys. Otherwise, segments are keyed by index ('0', '1', ...).

typescript
function parseEntityURI(
  uri: string,
  uriPath?: EntityURIPath
): { type: string; id: Record<string, string> }
ParameterTypeRequiredDescription
uristringYes
uriPathEntityURIPathNo

Returns: { type: string; id: Record&lt;string, string&gt; }

parseEntityURIWithLabel()

Parse an entity URI and also extract the display label if present.

typescript
function parseEntityURIWithLabel(
  uri: string,
  uriPath?: EntityURIPath
): { type: string; id: Record<string, string>; label?: string }
ParameterTypeRequiredDescription
uristringYes
uriPathEntityURIPathNo

Returns: { type: string; id: Record&lt;string, string&gt;; label?: string }

getEntityTypeFromURI()

Extract just the entity type from a URI without full parsing.

typescript
function getEntityTypeFromURI(uri: string): string
ParameterTypeRequiredDescription
uristringYes

Returns: string

isEntityURI()

Check whether a string is a valid entity URI (non-throwing).

typescript
function isEntityURI(uri: string): boolean
ParameterTypeRequiredDescription
uristringYes

Returns: boolean

extractLabel()

typescript
function extractLabel(uri: string): string | undefined
ParameterTypeRequiredDescription
uristringYes

Returns: string \| undefined

compareEntityURIs()

Compare two entity URIs for equality, ignoring labels.

typescript
function compareEntityURIs(uri1: string, uri2: string): boolean
ParameterTypeRequiredDescription
uri1stringYes
uri2stringYes

Returns: boolean

Usage example

typescript
import {
  buildEntityURI,
  parseEntityURI,
  isEntityURI,
  compareEntityURIs,
  DRIFT_URI_SCHEME,
} from '@tryvienna/sdk';

// Build
const uri = buildEntityURI('github_pr', { owner: 'acme', repo: 'app', number: '7' }, {
  segments: ['owner', 'repo', 'number'],
});
// => '@drift//github_pr/acme/app/7'

// Parse
const { type, id } = parseEntityURI(uri, { segments: ['owner', 'repo', 'number'] });
// => { type: 'github_pr', id: { owner: 'acme', repo: 'app', number: '7' } }

// Validate
isEntityURI('@drift//project/abc');  // true
isEntityURI('not-a-uri');            // false

// Compare (ignores labels)
compareEntityURIs(
  '@drift//project/abc?label=Zm9v',
  '@drift//project/abc',
); // true

React Hooks

Import from @tryvienna/sdk/react (or re-exported from the root). These hooks read the Apollo client from <PluginDataProvider> — plugins never import Apollo directly.

useEntity()

typescript
function useEntity(uri: string, options: UseEntityOptions = {}): UseEntityResult
ParameterTypeRequiredDescription
uristringYes
optionsUseEntityOptionsNo

Returns: UseEntityResult

UseEntityOptions

PropertyTypeRequiredDescription
fetchPolicyWatchQueryFetchPolicyNo
pollIntervalnumberNo
skipbooleanNo

UseEntityResult

PropertyTypeRequiredDescription
entityBaseEntity | nullYes
loadingbooleanYes
errorError | undefinedYes
refetch() =&gt; Promise&lt;unknown&gt;Yes
tsx
import { useEntity } from '@tryvienna/sdk/react';

function PRDetail({ uri }: { uri: string }) {
  const { entity, loading, error, refetch } = useEntity(uri);

  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  if (!entity) return <NotFound />;

  return <div>{entity.title}</div>;
}

useEntities()

typescript
function useEntities(options: UseEntitiesOptions): UseEntitiesResult
ParameterTypeRequiredDescription
optionsUseEntitiesOptionsYes

Returns: UseEntitiesResult

UseEntitiesOptions

PropertyTypeRequiredDescription
typestringYes
querystringNo
filtersRecord&lt;string, unknown&gt;No
limitnumberNo
fetchPolicyWatchQueryFetchPolicyNo
pollIntervalnumberNo
skipbooleanNo

UseEntitiesResult

PropertyTypeRequiredDescription
entitiesBaseEntity[]Yes
loadingbooleanYes
errorError | undefinedYes
tsx
import { useEntities } from '@tryvienna/sdk/react';

function InboxList() {
  const { entities, loading } = useEntities({
    type: 'google_gmail_thread',
    query: 'in:inbox',
    limit: 20,
    pollInterval: 30_000,
  });

  return (
    <ul>
      {entities.map((e) => (
        <li key={e.uri}>{e.title}</li>
      ))}
    </ul>
  );
}

usePluginQuery()

Run custom GraphQL queries through the plugin data context. Supports full type inference with TypedDocumentNode from codegen.

typescript
// With TypedDocumentNode (codegen) — types inferred automatically
function usePluginQuery<TData, TVariables>(
  query: TypedDocumentNode<TData, TVariables>,
  options?: Omit<QueryHookOptions<TData, TVariables>, "client">,
): QueryResult<TData, TVariables>

// With plain DocumentNode — pass type parameters manually
function usePluginQuery<TData, TVariables>(
  query: DocumentNode,
  options?: Omit<QueryHookOptions<TData, TVariables>, "client">,
): QueryResult<TData, TVariables>
tsx
// With codegen — fully typed, no manual generics
import { usePluginQuery } from '@tryvienna/sdk/react';
import { GET_GITHUB_ISSUE } from '../client/operations';

const { data } = usePluginQuery(GET_GITHUB_ISSUE, {
  variables: { owner: 'foo', repo: 'bar', issueNumber: 1 },
});
// data?.githubIssue is fully typed

// Without codegen — manual type parameters
import { usePluginQuery, gql } from '@tryvienna/sdk/react';

const GET_REPOS = gql\`query { repos { name } }\`;
const { data } = usePluginQuery<{ repos: { name: string }[] }>(GET_REPOS);

usePluginMutation()

Run custom GraphQL mutations. Same overload pattern as usePluginQuery.

typescript
// With TypedDocumentNode (codegen) — types inferred automatically
function usePluginMutation<TData, TVariables>(
  mutation: TypedDocumentNode<TData, TVariables>,
  options?: Omit<MutationHookOptions<TData, TVariables>, "client">,
): MutationTuple<TData, TVariables>

// With plain DocumentNode — pass type parameters manually
function usePluginMutation<TData, TVariables>(
  mutation: DocumentNode,
  options?: Omit<MutationHookOptions<TData, TVariables>, "client">,
): MutationTuple<TData, TVariables>
tsx
import { usePluginMutation } from '@tryvienna/sdk/react';
import { MERGE_PR } from '../client/operations';

function MergeButton({ uri }: { uri: string }) {
  const [mergePR, { loading }] = usePluginMutation(MERGE_PR);

  return (
    <button onClick={() => mergePR({ variables: { uri } })} disabled={loading}>
      Merge
    </button>
  );
}

usePluginClient()

Access the raw Apollo client from the plugin data context.

typescript
function usePluginClient(): ApolloClient<any>

WARNING

Must be used within a <PluginDataProvider>. Throws if no provider is found.

useHostApi()

Access the host API for credential management, OAuth flows, and proxied fetch.

typescript
function useHostApi(): PluginHostApi

See PluginHostApi for the full interface.

invalidateEntity()

Evict a cached entity and refetch all active queries.

typescript
function invalidateEntity(
  client: ApolloClient<any>,
  typename: string,
  id?: string,
  keyFields?: Record<string, string>,
): void
typescript
import { usePluginClient, invalidateEntity } from '@tryvienna/sdk/react';

const client = usePluginClient();
// Invalidate by URI (Entity type uses 'uri' as keyField)
invalidateEntity(client, 'Entity', undefined, { uri });

updateCachedEntity()

Update specific fields on a cached entity without a network request.

typescript
function updateCachedEntity(
  client: ApolloClient<any>,
  typename: string,
  id: string,
  fields: Record<string, unknown>,
  keyFields?: Record<string, string>,
): void
typescript
import { usePluginClient, updateCachedEntity } from '@tryvienna/sdk/react';

const client = usePluginClient();
// Optimistically update a cached entity's title
updateCachedEntity(client, 'GitHubPR', 'pr-123', {
  state: 'merged',
  title: 'Updated title',
});

PluginDataProvider

Host app wraps plugin components with this provider to inject the Apollo client and host API. Plugins never use this directly.

tsx
// Host app usage:
import { PluginDataProvider } from '@tryvienna/sdk/react';

<PluginDataProvider client={apolloClient} hostApi={hostApi}>
  {pluginContent}
</PluginDataProvider>

gql

Re-exported from graphql-tag for convenience. Use to write inline GraphQL operations.

typescript
import { gql } from '@tryvienna/sdk/react';

const GET_REPOS = gql`
  query GetRepos {
    repos { name url }
  }
`;

Core Types

BaseEntity

The minimal entity shape returned by all entity queries. Every entity in the system satisfies this interface.

typescript
interface BaseEntity {
  id: string;
  type: string;
  uri: string;
  title: string;
  description?: string;
  createdAt?: number;
  updatedAt?: number;
  metadata?: Record<string, unknown>;
}

PluginIcon

Static icon asset for plugins, integrations, and entities.

typescript
type PluginIcon =
  | { svg: string }    // Inline SVG markup
  | { png: string }    // Base64-encoded PNG
  | { path: string }   // Relative path to icon file

SecureStorage

Scoped secure storage interface for integrations. Provides encrypted key-value storage scoped to a specific integration. Structurally identical to ScopedStorage from @vienna/secure-storage.

Methods

  • get(key: string): Promise&lt;string | null&gt;No description
  • set(key: string, value: string): Promise&lt;void&gt;No description
  • delete(key: string): Promise&lt;void&gt;No description
  • has(key: string): Promise&lt;boolean&gt;No description

PluginLogger

Structured logger interface provided to integrations and entities at runtime. Every plugin gets a logger pre-scoped with { plugin: pluginId }. Integration and entity handlers get further scoped loggers (e.g., { plugin: 'github', integration: 'github' } or { plugin: 'github', entity: 'github_pr' }). Call child() to create sub-loggers with additional bindings.

Methods

  • debug(msg: string, ctx?: Record&lt;string, unknown&gt;): voidNo description
  • info(msg: string, ctx?: Record&lt;string, unknown&gt;): voidNo description
  • warn(msg: string, ctx?: Record&lt;string, unknown&gt;): voidNo description
  • error(msg: string, ctx?: Record&lt;string, unknown&gt;): voidNo description
  • child(bindings: Record&lt;string, unknown&gt;): PluginLogger — Create a child logger with additional bindings merged into every log entry.

AuthContext

Context injected into integration's createClient and method handlers.

PropertyTypeRequiredDescription
storageSecureStorageYes
loggerPluginLoggerYes
oauthOAuthAccessorNo

EntityContext<TIntegrations>

Context provided to entity resolve/search/action handlers. Integration clients are pre-resolved and typed via the integrations map.

typescript
type EntityContext<TIntegrations> = {
  storage: SecureStorage;
  logger: PluginLogger;
  integrations: {
    [K in keyof TIntegrations]: IntegrationAccessor<ClientOf<TIntegrations[K]>>;
  };
}

IntegrationAccessor<TClient = unknown>

Accessor for a single integration's client and methods within an entity context.

PropertyTypeRequiredDescription
clientTClient | nullYes

SearchQuery

Search query passed to entity search handlers.

PropertyTypeRequiredDescription
querystringNo
limitnumberNo
offsetnumberNo
filtersRecord&lt;string, unknown&gt;No

ClientOf<T>

Infer the client type from an IntegrationDefinition.

typescript
type ClientOf<T> = T extends IntegrationDefinition<infer C> ? C : never

OAuth Types

OAuthConfig

OAuth configuration for an integration.

PropertyTypeRequiredDescription
providersOAuthProviderConfig[]Yes

OAuthProviderConfig

PropertyTypeRequiredDescription
providerIdstringYes
displayNamestringYes
iconstringNo
flowOAuthFlowConfigYes
refreshBufferSecondsnumberNo
requiredbooleanNo

OAuthFlowConfig

Union of the three supported grant types:

typescript
type OAuthFlowConfig =
  | OAuthAuthorizationCodeConfig
  | OAuthDeviceCodeConfig
  | OAuthManualCodeConfig

OAuthAuthorizationCodeConfig

PropertyTypeRequiredDescription
grantType'authorization_code'Yes
clientIdstringYes
clientSecretstringNo
clientIdKeystringNo
clientSecretKeystringNo
authorizationUrlstringYes
tokenUrlstringYes
scopesstring[]Yes
pkce{ enabled: boolean; method?: 'S256' | 'plain' }No
extraAuthParamsRecord&lt;string, string&gt;No
refreshUrlstringNo
redirectPathstringNo
redirectPortnumberNo
scopeSeparatorstringNo

OAuthDeviceCodeConfig

PropertyTypeRequiredDescription
grantType'device_code'Yes
clientIdstringYes
clientSecretstringNo
clientIdKeystringNo
clientSecretKeystringNo
deviceAuthorizationUrlstringYes
tokenUrlstringYes
scopesstring[]Yes
pollingIntervalnumberNo
refreshUrlstringNo

OAuthManualCodeConfig

PropertyTypeRequiredDescription
grantType'manual_code'Yes
clientIdstringYes
clientSecretstringNo
clientIdKeystringNo
clientSecretKeystringNo
authorizationUrlstringYes
tokenUrlstringYes
scopesstring[]Yes
instructionsstringYes
refreshUrlstringNo

OAuthTokenData

Token data stored after successful OAuth flow.

PropertyTypeRequiredDescription
accessTokenstringYes
refreshTokenstringNo
expiresAtnumberNo
scopesstring[]No
tokenTypestringNo
obtainedAtnumberNo
extraRecord&lt;string, unknown&gt;No

OAuthAccessor

OAuth accessor provided to integration's createClient.

Methods

  • getAccessToken(providerId: string): Promise&lt;string | null&gt;No description
  • getTokenData(providerId: string): Promise&lt;OAuthTokenData | null&gt;No description
  • isAuthenticated(providerId: string): Promise&lt;boolean&gt;No description

EntitySource

typescript
type EntitySource = 'builtin' | 'integration'

EntityDisplayMetadata

Display metadata for automatic entity styling in the UI.

typescript
interface EntityDisplayMetadata {
  emoji: string;
  colors: EntityDisplayColors;
  description?: string;
  filterDescriptions?: FilterDescription[];
  outputFields?: OutputField[];
}

interface EntityDisplayColors {
  bg: string;    // Background CSS color
  text: string;  // Text CSS color
  border: string; // Border CSS color
}

interface FilterDescription {
  name: string;
  type: string;
  description: string;
}

interface OutputField {
  key: string;
  label: string;
  metadataPath: string;
  format?: string;
}

Canvas Types

Plugins contribute UI to three canvas slots. Each canvas type has a config interface (what plugins provide) and a props interface (what the host injects at render time).

CanvasType

typescript
type CanvasType = 'nav-sidebar' | 'drawer' | 'menu-bar'

CanvasLogger

A stripped-down logger for canvas components. Same as PluginLogger but without child().

typescript
type CanvasLogger = Omit<PluginLogger, 'child'>

PluginHostApi

Methods

  • getCredentialStatus(integrationId: string): Promise&lt;CredentialStatusEntry[]&gt; — Check which credentials are configured for an integration.
  • setCredential(integrationId: string, key: string, value: string): Promise&lt;void&gt; — Set a credential for an integration (stored in OS-level encrypted storage).
  • removeCredential(integrationId: string, key: string): Promise&lt;void&gt; — Remove a credential for an integration.
  • startOAuthFlow(integrationId: string, providerId: string): Promise&lt;{ success: boolean; error?: string }&gt; — Start an OAuth authorization flow (opens browser for user to authorize).
  • getOAuthStatus(integrationId: string): Promise&lt;OAuthProviderStatusEntry[]&gt; — Get OAuth provider status for an integration.
  • revokeOAuthToken(integrationId: string, providerId: string): Promise&lt;{ success: boolean }&gt; — Revoke an OAuth token for a provider.
  • fetch(url: string, options?: PluginFetchOptions): Promise&lt;PluginFetchResult&gt; — Fetch an external URL via the main process (bypasses renderer CSP). Only domains declared in the plugin's allowedDomains are permitted.

CredentialStatusEntry

PropertyTypeRequiredDescription
keystringYes
isSetbooleanYes

OAuthProviderStatusEntry

PropertyTypeRequiredDescription
providerIdstringYes
displayNamestringNo
connectedbooleanYes
expiresAtnumberNo
scopesstring[]No
flowStatusstringNo
requiredbooleanNo
tsx
import { useHostApi } from '@tryvienna/sdk/react';

function GitHubSettings({ integrationId }: { integrationId: string }) {
  const hostApi = useHostApi();

  const handleConnect = async () => {
    const result = await hostApi.startOAuthFlow(integrationId, 'github');
    if (!result.success) console.error(result.error);
  };

  const handleSetToken = async (token: string) => {
    await hostApi.setCredential(integrationId, 'personal_access_token', token);
  };

  return <button onClick={handleConnect}>Connect GitHub</button>;
}
PropertyTypeRequiredDescription
componentComponentType&lt;NavSidebarCanvasProps&gt;Yes
labelstringYes
iconstringNo
prioritynumberNo
PropertyTypeRequiredDescription
pluginIdstringYes
openPluginDrawer(payload: TPayload) =&gt; voidYes
openEntityDrawer(uri: string) =&gt; voidYes
hostApiPluginHostApiYes
loggerCanvasLoggerYes
tsx
// Nav sidebar component — renders in the left sidebar
function GitHubSidebar({ pluginId, openEntityDrawer, hostApi, logger }: NavSidebarCanvasProps) {
  const { entities, loading } = useEntities({ type: 'github_pr', limit: 10 });

  return (
    <div>
      {entities.map((pr) => (
        <button key={pr.uri} onClick={() => openEntityDrawer(pr.uri)}>
          {pr.title}
        </button>
      ))}
    </div>
  );
}

Drawer

DrawerCanvasConfig<TPayload extends Record<string, unknown> = Record<string, unknown>>

PropertyTypeRequiredDescription
componentComponentType&lt;PluginDrawerCanvasProps&lt;TPayload&gt;&gt;Yes
footerComponentType&lt;PluginDrawerCanvasProps&lt;TPayload&gt;&gt;NoOptional footer component rendered pinned at the bottom of the drawer (outside scroll).
labelstringYes
iconstringNo

PluginDrawerCanvasProps<TPayload extends Record<string, unknown> = Record<string, unknown>>

PropertyTypeRequiredDescription
pluginIdstringYes
payloadTPayloadYes
drawerPluginDrawerActionsYes
openEntityDrawer(uri: string) =&gt; voidYes
hostApiPluginHostApiYes
loggerCanvasLoggerYes

PluginDrawerActions<TPayload extends Record<string, unknown> = Record<string, unknown>>

PropertyTypeRequiredDescription
close() =&gt; voidYes
open(payload: TPayload) =&gt; voidYes
push(payload: TPayload) =&gt; voidYes
pop() =&gt; voidYes
canPopbooleanYes
tsx
// Drawer component — plugin-level settings/detail panel
function GitHubDrawer({ pluginId, payload, drawer, hostApi }: PluginDrawerCanvasProps) {
  return (
    <div>
      <h2>GitHub Settings</h2>
      <button onClick={() => drawer.push({ view: 'tokens' })}>
        Manage Tokens
      </button>
      {drawer.canPop && (
        <button onClick={drawer.pop}>Back</button>
      )}
    </div>
  );
}
PropertyTypeRequiredDescription
iconComponentType&lt;MenuBarIconProps&gt;Yes
componentComponentType&lt;MenuBarCanvasProps&gt;Yes
labelstringYes
prioritynumberNo
PropertyTypeRequiredDescription
pluginIdstringYes
onClose() =&gt; voidYes
openPluginDrawer(payload: TPayload) =&gt; voidYes
hostApiPluginHostApiYes
loggerCanvasLoggerYes
PropertyTypeRequiredDescription
pluginIdstringYes
hostApiPluginHostApiYes
loggerCanvasLoggerYes
tsx
// Menu bar icon — renders in the top-right icon bar
function WeatherIcon({ pluginId }: MenuBarIconProps) {
  return <span>🌤</span>;
}

// Menu bar popover — shown when the icon is clicked
function WeatherPopover({ pluginId, onClose }: MenuBarCanvasProps) {
  return (
    <div>
      <h3>Weather</h3>
      <p>72°F — Sunny</p>
      <button onClick={onClose}>Close</button>
    </div>
  );
}

PluginCanvases

PropertyTypeRequiredDescription
'nav-sidebar'NavSidebarCanvasConfigNo
drawerDrawerCanvasConfigNo
'menu-bar'MenuBarCanvasConfigNo

PluginFetchOptions

PropertyTypeRequiredDescription
methodstringNo
headersRecord&lt;string, string&gt;No
bodystringNo

PluginFetchResult

PropertyTypeRequiredDescription
okbooleanYes
statusnumberYes
statusTextstringYes
headersRecord&lt;string, string&gt;Yes
bodystringYes
tsx
// Fetch external API via the host (bypasses renderer CSP)
const hostApi = useHostApi();
const result = await hostApi.fetch('https://api.open-meteo.com/v1/forecast?latitude=40.7&longitude=-74.0', {
  method: 'GET',
  headers: { 'Accept': 'application/json' },
});
if (result.ok) {
  const data = JSON.parse(result.body);
}

Schema Builder

The SchemaBuilder interface provides a typed subset of the Pothos API for plugins to extend the GraphQL schema. Plugins receive it in their integration's schema callback — no direct Pothos dependency needed.

SchemaBuilder

Typed subset of the Pothos SchemaBuilder API for plugin schema extensions. Plugins import this type from @tryvienna/sdk and use it in their schema: (builder: SchemaBuilder) => void callbacks. The real Pothos builder is a superset that satisfies this interface.

Methods

  • objectRef&lt;Shape = unknown&gt;(name: string): ObjectRef&lt;Shape&gt; — Create a named reference to an object type (for use before type definition).
  • objectType&lt;Shape&gt;( ref: ObjectRef&lt;Shape&gt;, config: { description?: string; fields: (t: ObjectFieldBuilder) =&gt; Record&lt;string, unknown&gt;; }, ): void — Register an object type with its fields.
  • queryFields( fields: (t: RootFieldBuilder) =&gt; Record&lt;string, unknown&gt;, ): void — Register query fields.
  • mutationFields( fields: (t: RootFieldBuilder) =&gt; Record&lt;string, unknown&gt;, ): void — Register mutation fields.
  • inputType( name: string, config: { description?: string; fields: (t: InputFieldBuilder) =&gt; Record&lt;string, unknown&gt;; }, ): InputRef — Define an input type.
  • enumType&lt;Values extends readonly string[]&gt;( name: string, config: { description?: string; values: Values; }, ): EnumRef&lt;Values[number]&gt; — Define an enum type.
  • entityObjectType&lt;TData&gt;( entityDef: EntityDefinition, config: EntityObjectTypeConfig&lt;TData&gt;, ): ObjectRef&lt;TData&gt; — Create an entity-backed GraphQL object type with auto-generated base queries. This is the primary way plugins expose entities via GraphQL. It:
  1. Creates a Pothos object type with base entity fields + custom fields
  2. Auto-generates {camelType}(uri: String!) and {camelType}s(query, limit) queries
  3. Registers resolve/search/resolveContext handlers in the EntityRegistry
  • registerEntityHandlers&lt;TData&gt;( entityDef: EntityDefinition, config: EntityHandlerConfig&lt;TData&gt;, ): void — Register entity handlers (resolve/search/resolveContext) WITHOUT creating a new Pothos type. Use this when you've already defined the Pothos type manually but still need the EntityRegistry to know how to resolve/search this entity for MCP tools.
  • entityPayload&lt;TData&gt;( name: string, entityRef: ObjectRef&lt;TData&gt;, entityFieldName: string, ): ObjectRef&lt;EntityPayloadShape&lt;TData&gt;&gt; — Create a standard mutation payload type for entity mutations. Creates {name}Payload with fields:
  • success: Boolean!
  • message: String
  • [entityFieldName]: EntityType (typed, for cache invalidation)
  • data: JSON

EntityObjectTypeConfig<TData>

Configuration for entityObjectType().

PropertyTypeRequiredDescription
integrationsRecord&lt;string, IntegrationDefinition&lt;any&gt;&gt;NoIntegration dependencies — keys become typed accessors on ctx.integrations.
descriptionstringNoOptional description override (defaults to entity name).
fields(t: ObjectFieldBuilder) =&gt; Record&lt;string, unknown&gt;YesCustom fields beyond the base entity fields (id, type, uri, title, etc.).
resolve(id: Record&lt;string, string&gt;, ctx: EntityContext) =&gt; Promise&lt;TData | null&gt;NoResolve a single entity by its URI ID segments.
search(query: SearchQuery, ctx: EntityContext) =&gt; Promise&lt;TData[]&gt;NoSearch/list entities.
resolveContext(entity: TData, ctx: EntityContext) =&gt; Promise&lt;string&gt;NoGenerate context markdown for AI/MCP consumption.

EntityHandlerConfig<TData>

Configuration for registerEntityHandlers() — handler-only registration.

PropertyTypeRequiredDescription
integrationsRecord&lt;string, IntegrationDefinition&lt;any&gt;&gt;NoIntegration dependencies — keys become typed accessors on ctx.integrations.
resolve(id: Record&lt;string, string&gt;, ctx: EntityContext) =&gt; Promise&lt;TData | null&gt;NoResolve a single entity by its URI ID segments.
search(query: SearchQuery, ctx: EntityContext) =&gt; Promise&lt;TData[]&gt;NoSearch/list entities.
resolveContext(entity: TData, ctx: EntityContext) =&gt; Promise&lt;string&gt;NoGenerate context markdown for AI/MCP consumption.

EntityPayloadShape<_TData = unknown>

Shape of an entity mutation payload.

PropertyTypeRequiredDescription
successbooleanYes
messagestring | nullNo
entity_TData | nullNo
dataunknownNo

Example — extending the schema

typescript
import type { SchemaBuilder } from '@tryvienna/sdk';
import { githubPrEntity } from './entities';
import { githubIntegration } from './integration';

export function registerGitHubSchema(b: SchemaBuilder): void {
  // Create entity-backed type with auto-generated queries
  const GitHubPR = b.entityObjectType<PRData>(githubPrEntity, {
    integrations: { github: githubIntegration },
    fields: (t) => ({
      number: t.exposeInt('number'),
      state: t.exposeString('state'),
      author: t.exposeString('author'),
      additions: t.exposeInt('additions'),
      deletions: t.exposeInt('deletions'),
    }),
    resolve: async (id, ctx) => {
      const client = ctx.integrations.github.client;
      if (!client) return null;
      return client.getPR(id.owner, id.repo, Number(id.number));
    },
    search: async (query, ctx) => {
      const client = ctx.integrations.github.client;
      if (!client) return [];
      return client.searchPRs(query.query ?? '', query.limit);
    },
  });

  // Mutation payload
  const MergePayload = b.entityPayload('MergeGitHubPr', GitHubPR, 'pr');

  b.mutationFields((t) => ({
    mergeGitHubPr: t.field({
      type: MergePayload,
      args: { uri: t.arg.string({ required: true }) },
      resolve: async (_, args, ctx) => {
        // ... merge logic
        return { success: true, entity: mergedPR };
      },
    }),
  }));
}

Registries

Runtime registries that hold definitions and route operations. Most plugins use PluginSystem (the unified registry); the lower-level EntityRegistry and IntegrationRegistry are used internally by @vienna/graphql.

PluginSystem

Methods

MethodReturnsDescription
registerPlugin(plugin: PluginDefinition): voidvoid
unregisterPlugin(id: string): booleanboolean
getPlugin(id: string): PluginDefinition | undefinedPluginDefinition | undefined
getPlugins(): PluginDefinition[]PluginDefinition[]
getPluginIds(): string[]string[]
getIntegration(id: string): IntegrationDefinition | undefinedIntegrationDefinition | undefined
getAllIntegrations(): IntegrationDefinition[]IntegrationDefinition[]
getPluginForIntegration(integrationId: string): string | undefinedstring | undefinedGet the plugin ID that registered a given integration.
getEntity(type: string): EntityDefinition | undefinedEntityDefinition | undefined
getEntityTypes(): string[]string[]
getAllEntities(): EntityDefinition[]EntityDefinition[]
registerEntityHandlers(type: string, handlers: EntityHandlers&lt;TData&gt;): voidvoidRegister resolve/search/resolveContext handlers for an entity type.
getEntityHandlers(type: string): EntityHandlers | undefinedEntityHandlers | undefined
resolveEntity(uri: string, ctx: EntityContext): Promise&lt;BaseEntity | null&gt;Promise&lt;BaseEntity | null&gt;
searchEntities(query: string, ctx: EntityContext, types?: string[], limit?: number): Promise&lt;BaseEntity[]&gt;Promise&lt;BaseEntity[]&gt;
resolveEntityContext(uri: string, ctx: EntityContext): Promise&lt;string | null&gt;Promise&lt;string | null&gt;
getEntityTypeSummaries(): EntityTypeSummary[]EntityTypeSummary[]
getNavCanvases(): ResolvedNavSidebar[]ResolvedNavSidebar[]
getDrawerCanvas(pluginId: string): ResolvedDrawer | undefinedResolvedDrawer | undefined
getMenuBarItems(): ResolvedMenuBar[]ResolvedMenuBar[]
getEntityDrawer(type: string): ResolvedEntityDrawer | undefinedResolvedEntityDrawer | undefined
getEntityDrawers(): ResolvedEntityDrawer[]ResolvedEntityDrawer[]

EntityHandlers<TData = BaseEntity>

PropertyTypeRequiredDescription
resolve(id: Record&lt;string, string&gt;, ctx: EntityContext) =&gt; Promise&lt;TData | null&gt;NoResolve a single entity by its URI ID segments.
search(query: SearchQuery, ctx: EntityContext) =&gt; Promise&lt;TData[]&gt;NoSearch/list entities.
resolveContext(entity: TData, ctx: EntityContext) =&gt; Promise&lt;string&gt;NoGenerate context markdown for AI/MCP consumption.
integrationDepsRecord&lt;string, string&gt;NoIntegration dependencies — maps local names to integration IDs.

EntityRegistry

Methods

MethodReturnsDescription
register(definition: EntityDefinition): voidvoidRegister an entity definition. Throws if the type is already registered.
registerHandlers(type: string, entityHandlers: EntityHandlers&lt;TData&gt;): voidvoidRegister resolve/search/resolveContext handlers for an entity type.
Called by entityObjectType() in the schema builder wrapper.
The entity definition must already be registered.
unregister(type: string): booleanbooleanUnregister an entity type and its handlers. Returns true if it existed.
getDefinition(type: string): EntityDefinition | undefinedEntityDefinition | undefinedGet the definition for a type.
getHandlers(type: string): EntityHandlers | undefinedEntityHandlers | undefinedGet the handlers for a type.
getTypes(): string[]string[]Get all registered type names.
getAllDefinitions(): EntityDefinition[]EntityDefinition[]Get all definitions.
getTypeSummaries(): EntityTypeSummary[]EntityTypeSummary[]Get type summaries for discovery (entityTypes query, MCP entity_types tool).
getByURI(uri: string, ctx: EntityContext): Promise&lt;BaseEntity | null&gt;Promise&lt;BaseEntity | null&gt;Resolve a single entity by URI.
search(query: string, ctx: EntityContext, types?: string[], limit?: number): Promise&lt;BaseEntity[]&gt;Promise&lt;BaseEntity[]&gt;Search across entity types.
resolveContext(uri: string, ctx: EntityContext): Promise&lt;string | null&gt;Promise&lt;string | null&gt;Resolve context markdown for an entity (used by MCP/AI).

IntegrationRegistry

Methods

MethodReturnsDescription
register(definition: IntegrationDefinition): voidvoidRegister an integration definition. Throws if the ID is already registered.
unregister(id: string): booleanbooleanUnregister an integration. Returns true if it existed.
getDefinition(id: string): IntegrationDefinition | undefinedIntegrationDefinition | undefinedGet a specific integration definition.
getAllDefinitions(): IntegrationDefinition[]IntegrationDefinition[]Get all registered integration definitions.

Resolved Canvas Types

Returned by PluginSystem canvas query methods. Pairs the canvas config with the owning plugin ID.

ResolvedNavSidebar

PropertyTypeRequiredDescription
pluginIdstringYes
configNavSidebarCanvasConfigYes

ResolvedDrawer

PropertyTypeRequiredDescription
pluginIdstringYes
configDrawerCanvasConfigYes

ResolvedMenuBar

PropertyTypeRequiredDescription
pluginIdstringYes
configMenuBarCanvasConfigYes

ResolvedEntityDrawer

PropertyTypeRequiredDescription
entityTypestringYes
pluginIdstringYes
componentComponentType&lt;EntityDrawerProps&gt;Yes
cardComponentType&lt;EntityCardProps&gt;No

Cache

EntityCache<V>

Constructor

ParameterTypeRequired
configEntityCacheConfigYes

Properties

PropertyTypeDescription
sizenumberNumber of live (non-expired) entries. Prunes expired entries first.

Methods

MethodReturnsDescription
get(key: string): V | undefinedV | undefinedGet a value, returning undefined if expired or missing.
set(key: string, value: V): voidvoidSet a value, evicting the oldest entry if at capacity.
invalidate(key: string): voidvoidRemove a specific entry.
prune(): numbernumberRemove all expired entries. Returns the number of entries removed.
clear(): voidvoidClear all entries.
typescript
import { EntityCache } from '@tryvienna/sdk';

const cache = new EntityCache<PRData>({ ttl: 30_000, maxSize: 200 });

cache.set('key', prData);
const hit = cache.get('key');     // PRData | undefined
cache.invalidate('key');          // Remove specific entry
const pruned = cache.prune();     // Remove expired, returns count
cache.clear();                    // Remove all

Errors

EntityURIError

Error thrown when URI parsing or building fails.

Constructor

ParameterTypeRequired
codeEntityURIErrorCodeYes
messagestringYes
uristringNo

Properties

PropertyTypeDescription
name"EntityURIError"

Error codes: INVALID_FORMAT · MISSING_ENTITY_TYPE · MISSING_PATH · INVALID_ENTITY_TYPE · INVALID_PATH_SEGMENT · INVALID_LABEL_ENCODING · SEGMENT_COUNT_MISMATCH

EntityDefinitionError

Error thrown by defineEntity/defineIntegration for invalid configuration.

Constructor

ParameterTypeRequired
entityTypestringYes
fieldstringYes
messagestringYes

Properties

PropertyTypeDescription
name"EntityDefinitionError"

Type Guards

typescript
function isEntityURIError(error: unknown): error is EntityURIError
function isEntityDefinitionError(error: unknown): error is EntityDefinitionError
typescript
import { parseEntityURI, isEntityURIError } from '@tryvienna/sdk';

try {
  parseEntityURI('bad-uri');
} catch (err) {
  if (isEntityURIError(err)) {
    console.log(err.code); // 'INVALID_FORMAT'
    console.log(err.uri);  // 'bad-uri'
  }
}

Testing Utilities

In-memory mocks and a structured test harness. Import from @tryvienna/sdk.

LogEntry

PropertyTypeRequiredDescription
levelstringYes
msgstringYes
ctxRecord&lt;string, unknown&gt;No

MockSecureStorage

Properties

PropertyTypeDescription
sizenumber

Methods

MethodReturnsDescription
get(key: string): Promise&lt;string | null&gt;Promise&lt;string | null&gt;
set(key: string, value: string): Promise&lt;void&gt;Promise&lt;void&gt;
delete(key: string): Promise&lt;void&gt;Promise&lt;void&gt;
has(key: string): Promise&lt;boolean&gt;Promise&lt;boolean&gt;
clear(): voidvoid

MockPluginLogger

Constructor

ParameterTypeRequired
bindingsRecord&lt;string, unknown&gt;No

Properties

PropertyTypeDescription
entriesLogEntry[]

Methods

MethodReturnsDescription
debug(msg: string, ctx?: Record&lt;string, unknown&gt;): voidvoid
info(msg: string, ctx?: Record&lt;string, unknown&gt;): voidvoid
warn(msg: string, ctx?: Record&lt;string, unknown&gt;): voidvoid
error(msg: string, ctx?: Record&lt;string, unknown&gt;): voidvoid
child(childBindings: Record&lt;string, unknown&gt;): MockPluginLoggerMockPluginLogger
clear(): voidvoid

MockOAuthAccessor

Methods

MethodReturnsDescription
getAccessToken(providerId: string): Promise&lt;string | null&gt;Promise&lt;string | null&gt;
getTokenData(providerId: string): Promise&lt;OAuthTokenData | null&gt;Promise&lt;OAuthTokenData | null&gt;
isAuthenticated(providerId: string): Promise&lt;boolean&gt;Promise&lt;boolean&gt;
setToken(providerId: string, token: OAuthTokenData): voidvoid
removeToken(providerId: string): voidvoid
clear(): voidvoid

MockIntegrationAccessor<TClient = unknown>

Constructor

ParameterTypeRequired
clientTClient | nullNo

Properties

PropertyTypeDescription
clientTClient | null

Methods

MethodReturnsDescription
clear(): voidvoid

createMockEntityContext()

Create a mock EntityContext for testing. Pass integration accessors keyed by local name matching the entity's integrations config.

typescript
function createMockEntityContext(
  integrations: Record<string, IntegrationAccessor> = {},
): {
  ctx: EntityContext;
  storage: MockSecureStorage;
  logger: MockPluginLogger;
}
ParameterTypeRequiredDescription
integrationsRecord&lt;string, IntegrationAccessor&gt;No

Returns: { ctx: EntityContext; storage: MockSecureStorage; logger: MockPluginLogger; }

createTestHarness()

Create a test harness for an entity definition. Provides mock storage, logger, and context for testing. Since EntityDefinition is metadata-only, the harness just provides the mock context and delegates createURI/parseURI.

typescript
function createTestHarness(
  definition: EntityDefinition,
  integrations: Record<string, IntegrationAccessor> = {},
): EntityTestHarness
ParameterTypeRequiredDescription
definitionEntityDefinitionYes
integrationsRecord&lt;string, IntegrationAccessor&gt;No

Returns: EntityTestHarness

EntityTestHarness

PropertyTypeRequiredDescription
storageMockSecureStorageYes
loggerMockPluginLoggerYes
ctxEntityContextYes
definitionEntityDefinitionYes

Methods

  • createURI(id: Record&lt;string, string&gt;): stringNo description
  • parseURI(uri: string): { type: string; id: Record&lt;string, string&gt; }No description

Example

typescript
import { describe, it, expect } from 'vitest';
import { createTestHarness, MockIntegrationAccessor } from '@tryvienna/sdk';
import { githubPrEntity } from './entities';

describe('GitHub PR Entity', () => {
  it('creates and parses URIs', () => {
    const harness = createTestHarness(githubPrEntity);

    const uri = harness.createURI({ owner: 'acme', repo: 'app', number: '42' });
    expect(uri).toBe('@drift//github_pr/acme/app/42');

    const { id } = harness.parseURI(uri);
    expect(id.owner).toBe('acme');
    expect(id.number).toBe('42');
  });

  it('provides mock context for handler tests', () => {
    const mockGitHub = new MockIntegrationAccessor(mockGitHubClient);
    const harness = createTestHarness(githubPrEntity, { github: mockGitHub });

    // harness.ctx can be passed to resolve/search handlers
    expect(harness.ctx.integrations.github.client).toBe(mockGitHubClient);
    expect(harness.logger.entries).toEqual([]);
  });
});

Codegen

Import from @tryvienna/sdk/codegen. Creates a standard @graphql-codegen/client-preset configuration for plugins.

createPluginCodegenConfig()

typescript
function createPluginCodegenConfig(options?: PluginCodegenOptions): CodegenConfig
OptionTypeDefaultDescription
schemaPathstring'../graphql/schema.graphql'Path to the shared schema
documentsGlobstring'src/client/operations.ts'Glob for operation documents
outputDirstring'./src/client/generated/'Output directory for types
typescript
// In your plugin's codegen.ts:
import { createPluginCodegenConfig } from '@tryvienna/sdk/codegen';

export default createPluginCodegenConfig();
// Or with custom paths:
export default createPluginCodegenConfig({
  schemaPath: '../../packages/graphql/schema.graphql',
  documentsGlob: 'src/**/*.graphql',
});

Zod Schemas

All TypeScript types in the SDK derive from Zod schemas via z.infer<>. These are the validation source of truth — use them for runtime validation at system boundaries.

SchemaDescription
EntityTypeSchemaEntity type ID: lowercase alphanumeric + underscore, 1-64 chars, starts with letter.
PathSegmentSchemaURI path segment: non-empty, max 256 chars, no control characters.
EntityURIPathSchemaURI path config: { segments: readonly string[] } with at least one segment.
BaseEntitySchemaMinimal entity: { id, type, uri, title, description?, createdAt?, updatedAt?, metadata? }.
EntitySourceSchemaEnum: `'builtin'
EntityDisplayColorsSchemaColor triplet: { bg, text, border } as CSS color strings.
EntityDisplayMetadataSchemaFull display config: { emoji, colors, description?, filterDescriptions?, outputFields? }.
PaletteFilterSpecSchemaFilter spec for the command palette: { key, label, aliases?, values[] }.
EntityCacheConfigSchemaCache config: { ttl: number, maxSize?: number }.
EntityTypeSummarySchemaDiscovery summary: { type, displayName, icon, source, uriExample, display? }.
PluginIconSchemaUnion: `
IntegrationSummarySchemaIntegration discovery: { id, name, icon, description?, hasOAuth, status, credentials? }.
typescript
import { EntityTypeSchema, BaseEntitySchema } from '@tryvienna/sdk';

// Validate at runtime
const result = EntityTypeSchema.safeParse('github_pr');
if (result.success) {
  // result.data is a validated EntityType
}

// Infer TypeScript types
type EntityType = z.infer<typeof EntityTypeSchema>;
type BaseEntity = z.infer<typeof BaseEntitySchema>;