npm.io
1.2.1 • Published yesterday

@team-plain/graphql

Licence
MIT
Version
1.2.1
Deps
2
Size
54.8 MB
Vulns
0
Weekly
0
Stars
1

@team-plain/graphql

A typed TypeScript SDK for Plain's GraphQL API, auto-generated from the schema using a custom codegen pipeline.

Installation

npm install @team-plain/graphql

Requires Node 24+. Supports both ESM and CJS.

Usage

import { PlainClient } from "@team-plain/graphql";

const client = new PlainClient({ apiKey: "plainApiKey_xxx" });
Query

Queries are accessed via client.query:

const customer = await client.query.customer({ customerId: "c_123" });
console.log(customer.fullName);

// Relations are lazy-loaded — accessing them makes a separate API call
const company = await customer.company;
console.log(company.name);
Mutation

Mutations are accessed via client.mutation. Mutation errors are returned as typed data, not thrown as exceptions. This matches Plain's API where all mutations return *Output types with an optional error field.

const result = await client.mutation.upsertCustomer({
  input: {
    identifier: { emailAddress: "alice@example.com" },
    onCreate: {
      fullName: "Alice",
      email: { email: "alice@example.com", isVerified: false },
    },
    onUpdate: {},
  },
});

if (result.error) {
  // Typed MutationError with message, type, code, and field-level errors
  console.error(result.error.message);
  result.error.fields?.forEach((f) => {
    console.error(`  ${f.field}: ${f.message}`);
  });
} else {
  console.log(result.customer?.id);
}
Pagination
const customers = await client.query.customers({ first: 10 });

for (const customer of customers.nodes) {
  console.log(customer.fullName);
}

// Fetch the next page
const nextPage = await customers.fetchNext();
Union Types

GraphQL union and interface fields are exposed as discriminated unions of model classes. Each union member has a __typename property for narrowing and supports the same lazy-loading as any other model.

const thread = await client.query.thread({ threadId: "t_123" });

// Narrow with __typename
if (thread.createdBy.__typename === "UserActor") {
  console.log(thread.createdBy.userId);

  // Lazy-load a relation on the union member
  const user = await thread.createdBy.user;
  console.log(user?.fullName);
}

// Or narrow with instanceof
import { UserActorModel } from "@team-plain/graphql";

if (thread.createdBy instanceof UserActorModel) {
  const user = await thread.createdBy.user;
}

// Value-like unions — scalars available immediately
if (thread.statusDetail?.__typename === "ThreadStatusDetailWaitingForDuration") {
  console.log(thread.statusDetail.waitingUntil);
}

// List of unions
for (const identity of customer.identities) {
  if (identity.__typename === "EmailCustomerIdentity") {
    console.log(identity.email);
  }
}

Error Handling

  • Queries: network, auth (401), forbidden (403), and rate limit (429) errors throw typed exceptions (AuthenticationError, ForbiddenError, RateLimitError, NetworkError, PlainGraphQLError).
  • Mutations: return the full *Output type. Check result.error for a typed MutationError with message, type, code, and fields[]. This is intentional — Plain's API treats mutation errors as data.

Migrating from @team-plain/typescript-sdk

If you're migrating from the old @team-plain/typescript-sdk package, see the Migration Guide for a full breakdown of breaking changes including error handling, method renames, enum changes, and before/after examples.

Resources

License

MIT