Skip to main content Inicio Creadores jeremylongshore tons-of-skills-marketplace linear-sdk-patterns
linear-sdk-patterns TypeScript/JavaScript SDK patterns and best practices for Linear.
Use when learning SDK idioms, implementing pagination,
filtering, relation loading, or custom GraphQL queries.
Trigger: "linear SDK patterns", "linear best practices",
"linear typescript", "linear API patterns", "linear pagination".
Ir a la instalación Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/jeremylongshore/tons-of-skills-marketplace --skill linear-sdk-patternsEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Más de este repositorio langchain-deploy-integration Deploy a LangChain 1.0 / LangGraph 1.0 app to Cloud Run, Vercel, or LangServe correctly — with timeouts sized for chain length, cold-start mitigation, SSE anti-buffering headers, and Secret Manager over .env. Use when prepping a first production deploy, debugging a stream that hangs behind a proxy, or diagnosing p99 latency spikes. Trigger with "langchain deploy", "langchain cloud run", "langchain vercel python", "langchain langserve", or "langchain docker".
langchain-langgraph-agents Build a correct LangGraph 1.0 ReAct agent with create_react_agent — typed tools, error propagation, recursion caps, and stop conditions that actually stop. Use when writing a first tool-calling agent, migrating from AgentExecutor or initialize_agent, or diagnosing an agent that loops on vague prompts. Trigger with "langgraph agent", "create_react_agent", "langgraph tool calling", "AgentExecutor migration", or "agent loop cost".
langchain-langgraph-human-in-loop Build LangGraph 1.0 human-in-the-loop approval flows with interrupt_before /
interrupt_after and Command(resume=...) — JSON-serializable state, clean
resume semantics, and UI wiring for approval decisions. Use when adding an
approval gate before an expensive tool call, wiring a Slack/web UI for agent
approvals, or debugging a graph that crashes on interrupt.
Trigger with "langgraph human in loop", "langgraph interrupt_before",
"langgraph approval flow", "Command resume", "langgraph HITL".
Explorador de archivos
2 archivos name linear-sdk-patterns description TypeScript/JavaScript SDK patterns and best practices for Linear.
Use when learning SDK idioms, implementing pagination,
filtering, relation loading, or custom GraphQL queries.
Trigger: "linear SDK patterns", "linear best practices",
"linear typescript", "linear API patterns", "linear pagination".
allowed-tools Read, Write, Edit, Grep version 1.12.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io> tags ["saas","linear","api","typescript"] compatibility Designed for Claude Code
Linear SDK Patterns
Overview
Production patterns for @linear/sdk. The SDK wraps Linear's GraphQL API with strongly-typed models, cursor-based pagination (fetchNext()/fetchPrevious()), lazy-loaded relations, and typed error classes. Understanding these patterns avoids N+1 queries and rate limit waste.
Prerequisites
@linear/sdk installed
TypeScript project with strict: true
Understanding of async/await and GraphQL concepts
Instructions
Pattern 1: Client Singleton
import { LinearClient } from "@linear/sdk" ;
let _client : LinearClient | null = null ;
export function getLinearClient ( ): LinearClient {
if (!_client) {
const apiKey = process.env .LINEAR_API_KEY ;
if (!apiKey) throw new Error ("LINEAR_API_KEY is required" );
_client = new LinearClient ({ apiKey });
}
return _client;
}
const clientCache = new Map <string , LinearClient >();
export function getClientForUser (userId : string , : ): {
(!clientCache. (userId)) {
clientCache. (userId, ({ accessToken }));
}
clientCache. (userId)!;
}
accessToken
string
LinearClient
if
has
set
new
LinearClient
return
get
Pattern 2: Cursor-Based Pagination Linear uses Relay-style cursor pagination. The SDK provides fetchNext() and fetchPrevious() helpers, plus raw pageInfo for manual control.
const firstPage = await client.issues ({ first : 50 });
console .log (`Page 1: ${firstPage.nodes.length} issues` );
if (firstPage.pageInfo .hasNextPage ) {
const secondPage = await firstPage.fetchNext ();
console .log (`Page 2: ${secondPage.nodes.length} issues` );
}
async function * paginateAll<T>(
fetchPage : (cursor ?: string ) => Promise <{
nodes : T[];
pageInfo : { hasNextPage : boolean ; endCursor : string };
}>
): AsyncGenerator <T> {
let cursor : string | undefined ;
let hasNext = true ;
while (hasNext) {
const page = await fetchPage (cursor);
for (const node of page.nodes ) yield node;
hasNext = page.pageInfo .hasNextPage ;
cursor = page.pageInfo .endCursor ;
}
}
for await (const issue of paginateAll (c => client.issues ({ first : 50 , after : c }))) {
console .log (`${issue.identifier} : ${issue.title} ` );
}
Pattern 3: Relation Loading (Avoiding N+1) SDK models lazy-load relations. Accessing .assignee triggers a separate API call. Use raw GraphQL to batch-fetch relations in one request.
const issues = await client.issues ({ first : 50 });
for (const issue of issues.nodes ) {
const assignee = await issue.assignee ;
console .log (`${issue.identifier} : ${assignee?.name} ` );
}
const response = await client.client .rawRequest (`
query TeamIssues($teamKey: String!) {
issues(first: 50, filter: { team: { key: { eq: $teamKey } } }) {
nodes {
id identifier title priority
assignee { name email }
state { name type }
labels { nodes { name color } }
project { name }
}
}
}
` , { teamKey : "ENG" });
async function enrichIssue (issue : any ) {
const [assignee, state, team, labels] = await Promise .all ([
issue.assignee ,
issue.state ,
issue.team ,
issue.labels (),
]);
return { ...issue, _assignee : assignee, _state : state, _team : team, _labels : labels.nodes };
}
Pattern 4: Filtering with Comparators Linear supports eq, neq, in, nin, lt, lte, gt, gte, startsWith, contains, and logical and/or operators.
const bugs = await client.issues ({
first : 50 ,
filter : {
priority : { lte : 2 },
state : { type : { nin : ["completed" , "canceled" ] } },
labels : { name : { eq : "Bug" } },
team : { key : { eq : "ENG" } },
},
});
const filtered = await client.issues ({
filter : {
or : [
{ assignee : { email : { eq : "alice@company.com" } } },
{ assignee : { email : { eq : "bob@company.com" } } },
],
state : { type : { eq : "started" } },
},
});
const results = await client.issueSearch ("authentication bug" );
const recent = await client.issues ({
filter : {
updatedAt : { gte : new Date (Date .now () - 24 * 60 * 60 * 1000 ).toISOString () },
},
orderBy : "updatedAt" ,
first : 100 ,
});
Pattern 5: Type-Safe Error Handling import { LinearError , InvalidInputLinearError } from "@linear/sdk" ;
type Result <T> = { ok : true ; data : T } | { ok : false ; error : string ; retryable : boolean };
async function safeCall<T>(fn : () => Promise <T>): Promise <Result <T>> {
try {
return { ok : true , data : await fn () };
} catch (error) {
if (error instanceof InvalidInputLinearError ) {
return { ok : false , error : `Invalid input: ${error.message} ` , retryable : false };
}
if (error instanceof LinearError ) {
const retryable = error.status === 429 || error.status === 503 ;
return { ok : false , error : `[${error.status} ] ${error.message} ` , retryable };
}
return { ok : false , error : String (error), retryable : false };
}
}
const result = await safeCall (() => client.issue ("issue-uuid" ));
if (result.ok ) {
console .log (result.data .title );
} else if (result.retryable ) {
console .warn ("Transient error, retry:" , result.error );
}
Pattern 6: Custom GraphQL Client Access the underlying LinearGraphQLClient for full control.
const graphQLClient = client.client ;
graphQLClient.setHeader ("X-Request-Id" , crypto.randomUUID ());
const data = await graphQLClient.rawRequest (`
query Cycle($id: String!) {
cycle(id: $id) {
id name startsAt endsAt
issues { nodes { identifier title state { name } } }
}
}
` , { id : "cycle-uuid" });
const batchResult = await graphQLClient.rawRequest (`
mutation BatchUpdate {
a: issueUpdate(id: "id1", input: { priority: 1 }) { success }
b: issueUpdate(id: "id2", input: { priority: 1 }) { success }
c: issueUpdate(id: "id3", input: { priority: 1 }) { success }
}
` );
Error Handling Error Cause Solution Cannot read properties of nullNullable relation not checked Use (await issue.assignee)?.name Type is not assignableSDK/TypeScript version mismatch Update @linear/sdk to latest Promise rejection unhandledMissing try/catch on async Wrap in safeCall() or .catch() Query complexity too highToo many nested relations Use rawRequest() with flat field selection
Examples
Create Issue with Full Metadata const teams = await client.teams ();
const eng = teams.nodes .find (t => t.key === "ENG" )!;
const states = await eng.states ();
const todo = states.nodes .find (s => s.type === "unstarted" )!;
const labels = await client.issueLabels ({ filter : { name : { eq : "Bug" } } });
await client.createIssue ({
teamId : eng.id ,
title : "Login page crashes on Safari" ,
description : "## Steps to reproduce\n1. Open login in Safari 17\n2. Click Sign in\n3. Crash" ,
stateId : todo.id ,
priority : 1 ,
labelIds : [labels.nodes [0 ].id ],
estimate : 3 ,
});
Resources