用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/personamanagmentlayer/pcl --skill graphql-expert命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | graphql-expert |
| version | 1.0.0 |
| description | Expert-level GraphQL API development with schema design, resolvers, and subscriptions |
| category | api |
| tags | ["graphql","api","apollo","schema","resolvers","subscriptions","relay"] |
| allowed-tools | ["Read","Write","Edit","Bash(node:*, npm:*, npx:*)"] |
Expert guidance for GraphQL API development, schema design, resolvers, subscriptions, and best practices for building type-safe, efficient APIs.
// Apollo Server 4 setup
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
// Type definitions
const typeDefs = `#graphql
type User {
id: ID!
email: String!
name: String!
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
published: Boolean!
tags: [String!]!
createdAt: DateTime!
updatedAt: DateTime!
}
type Query {
users(limit: Int = 10, offset: Int = 0): UsersConnection!
user(id: ID!): User
posts(filter: PostFilter, sort: SortOrder): [Post!]!
post(id: ID!): Post
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
updatePost(id: ID!, input: UpdatePostInput!): Post!
publishPost(id: ID!): Post!
}
type Subscription {
postPublished: Post!
userCreated: User!
}
input CreateUserInput {
email: String!
name: String!
password: String!
}
input UpdateUserInput {
email: String
name: String
}
input CreatePostInput {
title: String!
content: String!
tags: [String!]
}
input UpdatePostInput {
title: String
content: String
tags: [String!]
}
input PostFilter {
published: Boolean
authorId: ID
tag: String
}
type UsersConnection {
nodes: [User!]!
totalCount: Int!
pageInfo: PageInfo!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}
enum SortOrder {
NEWEST_FIRST
OLDEST_FIRST
TITLE_ASC
TITLE_DESC
}
scalar DateTime
`;
// Resolvers
const resolvers = {
Query: {
users: async (_, { limit, offset }, { dataSources }) => {
const users = await dataSources.userAPI.getUsers({ limit, offset });
totalCount = dataSources..();
{
: users,
totalCount,
: {
: offset + limit < totalCount,
: offset > ,
},
};
},
: (_, { id }, { dataSources }) => {
dataSources..(id);
},
: (_, { filter, sort }, { dataSources }) => {
dataSources..({ filter, sort });
},
: (_, { id }, { dataSources }) => {
dataSources..(id);
},
},
: {
: (_, { input }, { dataSources, user }) => {
(!(input.)) {
(, {
: { : },
});
}
dataSources..(input);
},
: (_, { id, input }, { dataSources, user }) => {
(user. !== id && !user.) {
(, {
: { : },
});
}
dataSources..(id, input);
},
: (_, { input }, { dataSources, user, pubsub }) => {
(!user) {
(, {
: { : },
});
}
post = dataSources..({
...input,
: user.,
});
post;
},
: (_, { id }, { dataSources, user, pubsub }) => {
post = dataSources..(id);
pubsub.(, { : post });
post;
},
},
: {
: {
: pubsub.([]),
},
: {
: pubsub.([]),
},
},
: {
: (parent, _, { dataSources }) => {
dataSources..(parent.);
},
},
: {
: (parent, _, { dataSources }) => {
dataSources..(parent.);
},
},
: ({
: ,
: ,
() {
value.();
},
() {
(value);
},
() {
(ast. === .) {
(ast.);
}
;
},
}),
};
server = ({
typeDefs,
resolvers,
: [
({ httpServer }),
],
});
{ url } = (server, {
: ({ req }) => {
token = req.. || ;
user = (token);
{
user,
: {
: (),
: (),
},
pubsub,
};
},
: { : },
});
import DataLoader from 'dataloader';
// Create DataLoaders
class UserAPI {
private loader: DataLoader<string, User>;
constructor() {
this.loader = new DataLoader(async (ids: readonly string[]) => {
// Batch fetch users
const users = await db.user.findMany({
where: { id: { in: [...ids] } },
});
// Return in same order as input ids
const userMap = new Map(users.map(u => [u.id, u]));
return ids.map(id => userMap.get(id) ?? null);
});
}
async getUserById(id: string): Promise<User | null> {
return this..(id);
}
(: []): <( | )[]> {
..(ids);
}
}
resolvers = {
: {
: (parent, _, { dataSources }) => {
dataSources..(parent.);
},
},
};
# codegen.yml
schema: './src/schema.graphql'
documents: './src/**/*.graphql'
generates:
src/generated/graphql.ts:
plugins:
- typescript
- typescript-resolvers
- typescript-operations
config:
useIndexSignature: true
contextType: '../context#Context'
mappers:
User: '../models#UserModel'
Post: '../models#PostModel'
// Generated types usage
import { Resolvers } from './generated/graphql';
const resolvers: Resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
return dataSources.userAPI.getUserById(id);
},
},
};
import { GraphQLError } from 'graphql';
class NotFoundError extends GraphQLError {
constructor(resource: string, id: string) {
super(`${resource} with id ${id} not found`, {
extensions: {
code: 'NOT_FOUND',
resource,
id,
},
});
}
}
class ValidationError extends GraphQLError {
constructor(message: string, field: string) {
super(message, {
extensions: {
code: 'BAD_USER_INPUT',
field,
},
});
}
}
// Usage
const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
const user = await dataSources.userAPI.getUserById(id);
if (!user) {
throw new NotFoundError('User', id);
}
return user;
},
},
};
// Context with user
interface Context {
user: User | null;
dataSources: DataSources;
}
// Auth directive
import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils';
import { defaultFieldResolver, GraphQLSchema } from 'graphql';
function authDirective(directiveName: string) {
return {
authDirectiveTypeDefs: `directive @${directiveName}(requires: Role = USER) on OBJECT | FIELD_DEFINITION`,
authDirectiveTransformer: (schema: GraphQLSchema) =>
mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const directive = getDirective(schema, fieldConfig, directiveName)?.[0];
if (directive) {
const { resolve = defaultFieldResolver } = fieldConfig;
const { requires } = directive;
fieldConfig.resolve = async (source, args, context, info) => {
if (!context.user) {
throw (, {
: { : },
});
}
(requires && !context...(requires)) {
(, {
: { : },
});
}
(source, args, context, info);
};
}
fieldConfig;
},
}),
};
}
typeDefs = ;
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
// Create WebSocket server
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
});
// Setup subscription server
useServer(
{
schema,
context: async (ctx) => {
const token = ctx.connectionParams?.authentication;
const user = await getUserFromToken(token);
return { user, pubsub };
},
},
wsServer
);
// Subscription resolvers
const resolvers = {
Subscription: {
postPublished: {
subscribe: (_, __, { pubsub }) =>
pubsub.asyncIterator(['POST_PUBLISHED']),
},
messageAdded: {
subscribe: withFilter(
(_, __, { pubsub }) => pubsub.asyncIterator(['MESSAGE_ADDED']),
{
payload.. === variables.;
}
),
},
},
};
import { ApolloClient, InMemoryCache, gql, useQuery, useMutation } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache(),
});
// Query
const GET_USERS = gql`
query GetUsers($limit: Int, $offset: Int) {
users(limit: $limit, offset: $offset) {
nodes {
id
name
email
}
totalCount
pageInfo {
hasNextPage
}
}
}
`;
function UserList() {
const { loading, error, data } = useQuery(GET_USERS, {
variables: { limit: 10, offset: 0 },
});
if (loading) return <p>Loading...;
(error) ;
(
);
}
= gql`;
() {
[createPost, { loading, error }] = (, {
: [],
});
= () => {
e.();
({
: {
: {
: ,
: ,
},
},
});
};
;
}
= gql`;
() {
{ data, loading } = ();
(loading) ;
;
}
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
createComplexityLimitRule(1000, {
scalarCost: 1,
objectCost: 10,
listFactor: 10,
}),
],
plugins: [
{
async requestDidStart() {
return {
async didResolveOperation({ request, document }) {
const complexity = getComplexity({
schema,
query: document,
variables: request.variables,
estimators: [
fieldExtensionsEstimator(),
simpleEstimator({ defaultComplexity: 1 }),
],
});
if (complexity > 1000) {
throw new GraphQLError(
`Query is too complex: ${complexity}. Maximum allowed: 1000`
);
}
},
};
},
},
],
});
// Users service
import { buildSubgraphSchema } from '@apollo/subgraph';
const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3")
type User @key(fields: "id") {
id: ID!
email: String!
name: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
`;
const resolvers = {
User: {
__resolveReference: async (reference, { dataSources }) => {
return dataSources.userAPI.getUserById(reference.id);
},
},
: {
: dataSources..(id),
: dataSources..(),
},
};
typeDefs = gql`;
resolvers = {
: {
: ({ : , : post. }),
},
: {
:
dataSources..(user.),
},
};
{ , } ;
gateway = ({
: ({
: [
{ : , : },
{ : , : },
],
}),
});
server = ({ gateway });
# Use clear, consistent naming
type User {
id: ID!
email: String!
createdAt: DateTime!
}
# Prefer input types over many arguments
input CreateUserInput {
email: String!
name: String!
}
mutation {
createUser(input: CreateUserInput!): User!
}
# Use enums for fixed sets
enum OrderStatus {
PENDING
CONFIRMED
SHIPPED
DELIVERED
}
# Design for pagination
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
}
❌ Exposing internal IDs: Use opaque IDs or UUIDs ❌ Overly nested queries: Limit query depth ❌ No pagination: Always paginate lists ❌ Resolving in mutations: Keep mutations focused ❌ Exposing database schema directly: Design API-first ❌ No DataLoader: Leads to N+1 queries ❌ Generic error messages: Provide actionable errors ❌ No versioning strategy: Plan for schema evolution
import { ApolloServer } from '@apollo/server';
import { describe, it, expect } from 'vitest';
describe('GraphQL Server', () => {
it('should fetch user by id', async () => {
const server = new ApolloServer({ typeDefs, resolvers });
const response = await server.executeOperation({
query: `
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`,
variables: { id: '1' },
});
expect(response.body.kind).toBe('single');
expect(response.body.singleResult.data?.user).toEqual({
id: '1',
name: 'Alice',
email: 'alice@example.com',
});
});
it('should create post', async () => {
const response = await server.executeOperation({
query: ,
: {
: {
: ,
: ,
},
},
});
(response...?.).();
});
});
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
}
type PostEdge {
cursor: String!
node: Post!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
import { GraphQLUpload } from 'graphql-upload-ts';
const typeDefs = gql`
scalar Upload
type Mutation {
uploadFile(file: Upload!): File!
}
`;
const resolvers = {
Upload: GraphQLUpload,
Mutation: {
uploadFile: async (_, { file }) => {
const { createReadStream, filename, mimetype } = await file;
const stream = createReadStream();
// Process upload
await saveFile(stream, filename);
return { id: '1', filename, mimetype };
},
},
};
Expert in Persona Control Language (PCL) - language design, compiler architecture, runtime systems, and ecosystem development
Expert system for designing, creating, and validating PCL skills with comprehensive domain knowledge extraction
Expert-level Docker containerization, image optimization, and container orchestration. Use this skill for building efficient Docker images, managing containers, and implementing Docker best practices.
基于 SOC 职业分类