Agent Skills by ALSEL
Anthropic Claudeデータ・分析⭐ リポ 0品質スコア 50/100

graphql

GraphQLはクライアントが必要なデータだけを正確に取得できる仕組みで、単一エンドポイント・型付きスキーマ・イントロスペクションが特徴です。しかしその柔軟性は諸刃の剣であり、適切な制御がなければクライアントが悪意のあるクエリを送信してサーバーをダウンさせるリスクがあります。

description の原文を見る

GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server.

SKILL.md 本文

GraphQL

GraphQLはクライアントが必要とする データだけを提供します。1つのエンドポイント、型付きスキーマ、イントロスペクション。しかし、強力さをもたらす柔軟性は、危険性ももたらします。適切な制御がない場合、クライアントはサーバーを停止させるようなクエリを作成できます。

このスキルはスキーマ設計、リゾルバー、N+1防止のためのDataLoader、マイクロサービス向けフェデレーション、Apollo/urqlによるクライアント統合をカバーしています。重要な洞察:GraphQLは契約です。スキーマはAPI仕様書です。注意深く設計しましょう。

2025年の教訓:GraphQLが常に正解とは限りません。単純なCRUDにはRESTがシンプルです。高性能な公開APIではRESTとキャッシュが勝ります。複雑なデータ関係と多様なクライアント要件がある場合にGraphQLを使用してください。

原則

  • スキーマファーストの設計 - スキーマが契約
  • DataLoaderでN+1クエリを防止
  • クエリの深さと複雑さを制限
  • 再利用可能な選択のためにフラグメントを使用
  • ミューテーションは汎用の更新操作ではなく、具体的であるべき
  • エラーはデータ - 予期された失敗のためにユニオン型を使用
  • Null可能性は意味を持つ - 意図的に設計する

機能

  • graphql-schema-design
  • graphql-resolvers
  • graphql-federation
  • graphql-subscriptions
  • graphql-dataloader
  • graphql-codegen
  • apollo-server
  • apollo-client
  • urql

スコープ

  • database-queries -> postgres-wizard
  • authentication -> authentication-oauth
  • rest-api-design -> backend
  • websocket-infrastructure -> backend

ツーリング

サーバー

  • @apollo/server - 用途:Apollo Server v4 注記:最も一般的なGraphQLサーバー
  • graphql-yoga - 用途:軽量な代替案 注記:サーバーレスに適している
  • mercurius - 用途:Fastify統合 注記:高速、JITを使用

クライアント

  • @apollo/client - 用途:フル機能のクライアント 注記:キャッシング、状態管理
  • urql - 用途:軽量な代替案 注記:より小さく、シンプル
  • graphql-request - 用途:シンプルなリクエスト 注記:最小限、キャッシングなし

ツール

  • graphql-codegen - 用途:型生成 注記:TypeScriptに必須
  • dataloader - 用途:N+1防止 注記:バッチとキャッシュ

パターン

スキーマ設計

型安全なスキーマと適切なNull可能性

使用するタイミング:任意のGraphQL APIを設計する場合

スキーマ設計:

スキーマはAPI契約です。Null可能性を意図的に設計してください
- Non-null フィールドは常に解決するか例外を投げます
type Query {
  # Non-null - ユーザーを返すか例外を投げます
  user(id: ID!): User!

  # Nullable - 見つからない場合はnullを返します
  userByEmail(email: String!): User

  # Non-null リストとNon-null アイテム
  users(limit: Int = 10, offset: Int = 0): [User!]!

  # ページネーション付きの検索
  searchUsers(
    query: String!
    first: Int
    after: String
  ): UserConnection!
}

type Mutation {
  # 複雑なミューテーション用の入力型
  createUser(input: CreateUserInput!): CreateUserPayload!
  updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
  deleteUser(id: ID!): DeleteUserPayload!
}

type Subscription {
  userCreated: User!
  messageReceived(roomId: ID!): Message!
}

# 入力型
input CreateUserInput {
  email: String!
  name: String!
  role: Role = USER
}

input UpdateUserInput {
  email: String
  name: String
  role: Role
}

# ペイロード型(エラーをデータとして)
type CreateUserPayload {
  user: User
  errors: [Error!]!
}

union UpdateUserPayload = UpdateUserSuccess | NotFoundError | ValidationError

type UpdateUserSuccess {
  user: User!
}

# Enum型
enum Role {
  USER
  ADMIN
  MODERATOR
}

# 関係を持つ型
type User {
  id: ID!
  email: String!
  name: String!
  role: Role!
  posts(limit: Int = 10): [Post!]!
  createdAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
  published: Boolean!
}

# ページネーション(Relay スタイル)
type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type UserEdge {
  node: User!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

N+1防止のためのDataLoader

データベースクエリをバッチして キャッシュ

使用するタイミング:関係を解決する場合

DataLoader:

DataLoaderなしで、10個の投稿とその著者を取得すると
11クエリが実行されます(投稿が1 + 著者が10)。
DataLoaderはこれを2クエリにバッチします。
import DataLoader from 'dataloader';

// リクエストごとにローダーを作成
function createLoaders(db) {
  return {
    userLoader: new DataLoader(async (ids) => {
      // すべてのユーザーを1つのクエリで取得
      const users = await db.user.findMany({
        where: { id: { in: ids } }
      });

      // idと同じ順序で返す
      const userMap = new Map(users.map(u => [u.id, u]));
      return ids.map(id => userMap.get(id) || null);
    }),

    postsByAuthorLoader: new DataLoader(async (authorIds) => {
      const posts = await db.post.findMany({
        where: { authorId: { in: authorIds } }
      });

      // 著者でグループ化
      const postsByAuthor = new Map();
      posts.forEach(post => {
        const existing = postsByAuthor.get(post.authorId) || [];
        postsByAuthor.set(post.authorId, [...existing, post]);
      });

      return authorIds.map(id => postsByAuthor.get(id) || []);
    })
  };
}

// コンテキストにアタッチ
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

app.use('/graphql', expressMiddleware(server, {
  context: async ({ req }) => ({
    db,
    loaders: createLoaders(db),
    user: req.user
  })
}));

// リゾルバー内で使用
const resolvers = {
  Post: {
    author: (post, _, { loaders }) => {
      return loaders.userLoader.load(post.authorId);
    }
  },
  User: {
    posts: (user, _, { loaders }) => {
      return loaders.postsByAuthorLoader.load(user.id);
    }
  }
};

Apollo Client キャッシング

正規化されたキャッシュと型ポリシー

使用するタイミング:クライアント側のデータ管理

Apollo Client キャッシング:

Apollo Clientはレスポンスをフラットなキャッシュに正規化します。
型ポリシーを設定してカスタムキャッシュ動作を定義してください。
import { ApolloClient, InMemoryCache } from '@apollo/client';

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        // ページネーションされたフィールド
        users: {
          keyArgs: ['query'],  // クエリごとに別にキャッシュ
          merge(existing = { edges: [] }, incoming, { args }) {
            // 無限スクロール用に追加
            if (args?.after) {
              return {
                ...incoming,
                edges: [...existing.edges, ...incoming.edges]
              };
            }
            return incoming;
          }
        }
      }
    },
    User: {
      keyFields: ['id'],  // ユーザーを識別する方法
      fields: {
        fullName: {
          read(_, { readField }) {
            // コンピューテッドフィールド
            return `${readField('firstName')} ${readField('lastName')}`;
          }
        }
      }
    }
  }
});

const client = new ApolloClient({
  uri: '/graphql',
  cache,
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network'
    }
  }
});

// フックを使用したクエリ
import { useQuery, useMutation } from '@apollo/client';

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

function UserProfile({ userId }) {
  const { data, loading, error } = useQuery(GET_USER, {
    variables: { id: userId }
  });

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

  return <div>{data.user.name}</div>;
}

// キャッシュ更新付きミューテーション
const CREATE_USER = gql`
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
      user {
        id
        name
        email
      }
      errors {
        field
        message
      }
    }
  }
`;

function CreateUserForm() {
  const [createUser, { loading }] = useMutation(CREATE_USER, {
    update(cache, { data: { createUser } }) {
      // ミューテーション後にキャッシュを更新
      if (createUser.user) {
        cache.modify({
          fields: {
            users(existing = []) {
              const newRef = cache.writeFragment({
                data: createUser.user,
                fragment: gql`
                  fragment NewUser on User {
                    id
                    name
                    email
                  }
                `
              });
              return [...existing, newRef];
            }
          }
        });
      }
    }
  });
}

コード生成

スキーマから型安全な操作を生成

使用するタイミング:TypeScriptプロジェクト

GraphQL Codegen:

スキーマと操作からTypeScript型を生成してください。
クエリレスポンスの型を手動で入力する必要はありません。
# インストール
npm install -D @graphql-codegen/cli
npm install -D @graphql-codegen/typescript
npm install -D @graphql-codegen/typescript-operations
npm install -D @graphql-codegen/typescript-react-apollo
// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: 'http://localhost:4000/graphql',
  documents: ['src/**/*.graphql', 'src/**/*.tsx'],
  generates: {
    './src/generated/graphql.ts': {
      plugins: [
        'typescript',
        'typescript-operations',
        'typescript-react-apollo'
      ],
      config: {
        withHooks: true,
        withComponent: false
      }
    }
  }
};

export default config;
# 生成を実行
npx graphql-codegen
// 使用 - 完全に型付け!
import { useGetUserQuery, useCreateUserMutation } from './generated/graphql';

function UserProfile({ userId }: { userId: string }) {
  const { data, loading } = useGetUserQuery({
    variables: { id: userId }  // 型チェック済み!
  });

  // data.userは完全に型付けされています
  return <div>{data?.user?.name}</div>;
}

ユニオン型を使用したエラー処理

予期されたエラーは例外ではなくデータとして

使用するタイミング:予期された方法で失敗する可能性のある操作

エラーをデータとして:

予期された失敗ケースにはユニオン型を使用してください。
GraphQL エラーは予期しない失敗用です。
# スキーマ
type Mutation {
  login(email: String!, password: String!): LoginResult!
}

union LoginResult = LoginSuccess | InvalidCredentials | AccountLocked

type LoginSuccess {
  user: User!
  token: String!
}

type InvalidCredentials {
  message: String!
}

type AccountLocked {
  message: String!
  unlockAt: DateTime
}
# リゾルバー
const resolvers = {
  Mutation: {
    login: async (_, { email, password }, { db }) => {
      const user = await db.user.findByEmail(email);

      if (!user || !await verifyPassword(password, user.hash)) {
        return {
          __typename: 'InvalidCredentials',
          message: 'Invalid email or password'
        };
      }

      if (user.lockedUntil && user.lockedUntil > new Date()) {
        return {
          __typename: 'AccountLocked',
          message: 'Account temporarily locked',
          unlockAt: user.lockedUntil
        };
      }

      return {
        __typename: 'LoginSuccess',
        user,
        token: generateToken(user)
      };
    }
  },

  LoginResult: {
    __resolveType(obj) {
      return obj.__typename;
    }
  }
};
# クライアント クエリ
const LOGIN = gql`
  mutation Login($email: String!, $password: String!) {
    login(email: $email, password: $password) {
      ... on LoginSuccess {
        user { id name }
        token
      }
      ... on InvalidCredentials {
        message
      }
      ... on AccountLocked {
        message
        unlockAt
      }
    }
  }
`;
// すべてのケースを処理
const result = data.login;
switch (result.__typename) {
  case 'LoginSuccess':
    setToken(result.token);
    redirect('/dashboard');
    break;
  case 'InvalidCredentials':
    setError(result.message);
    break;
  case 'AccountLocked':
    setError(`${result.message}. Try again at ${result.unlockAt}`);
    break;
}

危険な落とし穴

各リゾルバーが個別のデータベースクエリを作成する

重大度:CRITICAL

状況:個別にデータを取得するリゾルバーを記述します。10個の投稿とその著者のクエリは11のデータベースクエリを実行します。100個の投稿の場合、101クエリです。応答時間は秒単位になります。

兆候:

  • APIレスポンスが遅い
  • ログに同様のデータベースクエリが多い
  • パフォーマンスがリストサイズとともに低下

これが壊れる理由: GraphQLリゾルバーは独立して実行されます。バッチ処理がない場合、著者リゾルバーは各投稿に対して個別に実行されます。データベースは繰り返される類似クエリでハンマー打ちされます。

推奨される修正:

# DataLoaderを使用

import DataLoader from 'dataloader';

// リクエストごとにローダーを作成
const userLoader = new DataLoader(async (ids) => {
  const users = await db.user.findMany({
    where: { id: { in: ids } }
  });
  // 重要:入力idと同じ順序で返す
  const userMap = new Map(users.map(u => [u.id, u]));
  return ids.map(id => userMap.get(id));
});

// リゾルバー内で使用
const resolvers = {
  Post: {
    author: (post, _, { loaders }) =>
      loaders.userLoader.load(post.authorId)
  }
};

重要なポイント:

  1. リクエストごとに新しいローダーを作成する(キャッシング範囲用)
  2. 入力IDと同じ順序で結果を返す
  3. 欠落アイテムを処理する(スキップせずにnullを返す)

深くネストされたクエリがサーバーをDoS可能

重大度:CRITICAL

状況:スキーマに循環関係があります(user.posts.author.posts...)。クライアントが20レベル深いクエリを送信します。サーバーはそれを解決しようとしてタイムアウトするか、クラッシュします。

兆候:

  • 特定のクエリでサーバータイムアウト
  • メモリ枯渇
  • ネストされたクエリの遅いレスポンス

これが壊れる理由: GraphQLはクライアントに任意の有効なクエリ形状をリクエストさせます。制限がなければ、悪意のあるまたはバグのあるクライアントが指数関数的な作業を必要とするクエリを作成できます。正規のクエリでも誤ってあまりに深くなる可能性があります。

推奨される修正:

# クエリの深さと複雑さを制限

import depthLimit from 'graphql-depth-limit';
import { createComplexityLimitRule } from 'graphql-validation-complexity';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    // ネストの深さを制限
    depthLimit(10),

    // クエリの複雑さを制限
    createComplexityLimitRule(1000, {
      scalarCost: 1,
      objectCost: 2,
      listFactor: 10
    })
  ]
});

また考慮してください:

  • クエリタイムアウト制限
  • クライアントごとのレート制限
  • 永続化されたクエリ(事前登録されたクエリのみを許可)

イントロスペクションが本番環境で有効になっていてスキーマが公開される

重大度:HIGH

状況:イントロスペクション有効で本番環境にデプロイします。誰でもスキーマをクエリし、すべての型、ミューテーション、フィールド名を発見できます。攻撃者は正確に何を標的にするかを知っています。

兆候:

  • イントロスペクションクエリ経由でスキーマが表示
  • GraphQL Playgroundが本番環境でアクセス可能
  • 完全な型情報が公開

これが壊れる理由: イントロスペクションは開発とツーリングに不可欠ですが、本番環境では攻撃者へのロードマップになります。管理者ミューテーション、内部フィールド、廃止予定だが機能する古いAPIを見つけることができます。

推奨される修正:

# 本番環境ではイントロスペクションを無効化

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',
  plugins: [
    process.env.NODE_ENV === 'production'
      ? ApolloServerPluginLandingPageDisabled()
      : ApolloServerPluginLandingPageLocalDefault()
  ]
});

より良い方法:永続化されたクエリを使用 本番環境では事前登録されたクエリのみを許可

const server = new ApolloServer({
  typeDefs,
  resolvers,
  persistedQueries: {
    cache: new InMemoryLRUCache()
  }
});

認可がスキーマディレクティブのみでリゾルバーにない

重大度:HIGH

状況:認可を完全に @auth ディレクティブに依存しています。誰かがディレクティブを回避する方法を見つけるか、複雑なビジネスルールが単純なディレクティブに適合しません。認可が失敗します。

兆候:

  • 許可されていないデータへのアクセス
  • ビジネスルールが実装されていない
  • ディレクティブのみのセキュリティが回避される

これが壊れる理由: ディレクティブは単純なチェック向けですが、複雑なビジネスロジックを処理できません。「ユーザーは自分の投稿を編集でき、またはモデレートしているグループ内の任意の投稿を編集できる」はディレクティブに適合しません。

推奨される修正:

# リゾルバーで認可

// リゾルバーでシンプルなチェック
Mutation: {
  deletePost: async (_, { id }, { user, db }) => {
    if (!user) {
      throw new AuthenticationError('Must be logged in');
    }

    const post = await db.post.findUnique({ where: { id } });

    if (!post) {
      throw new NotFoundError('Post not found');
    }

    // ビジネスロジック認可
    const canDelete =
      post.authorId === user.id ||
      user.role === 'ADMIN' ||
      await userModeratesGroup(user.id, post.groupId);

    if (!canDelete) {
      throw new ForbiddenError('Cannot delete this post');
    }

    return db.post.delete({ where: { id } });
  }
}

// フィールドレベルの認可用ヘルパー
User: {
  email: (user, _, { currentUser }) => {
    // メールアドレスは本人または管理者のみに表示
    if (currentUser?.id === user.id || currentUser?.role === 'ADMIN') {
      return user.email;
    }
    return null;
  }
}

クエリの認可はあるがフィールドの認可がない

重大度:HIGH

状況:ユーザーがリソースにアクセスできるかはチェックしますが、個別フィールドはチェックしません。ユーザーAはユーザーBの公開プロフィールを見ることができ、偶然に彼らのプライベートメールと電話番号も見てしまいます。

兆候:

  • 機密データが公開されている
  • プライバシー違反
  • 誤ったユーザーに見えるフィールドデータ

これが壊れる理由: フィールドリゾルバーは親が返された後に実行されます。親クエリがユーザーを返す場合、すべてのフィールドが解決されます - 機密データも含めて。各機密フィールドは独自の認可チェックが必要です。

推奨される修正:

# フィールドレベルの認可

const resolvers = {
  User: {
    // 公開フィールド - チェック不要
    id: (user) => user.id,
    name: (user) => user.name,

    // プライベートフィールド - アクセスをチェック
    email: (user, _, { currentUser }) => {
      if (!currentUser) return null;
      if (currentUser.id === user.id) return user.email;
      if (currentUser.role === 'ADMIN') return user.email;
      return null;
    },

    phoneNumber: (user, _, { currentUser }) => {
      if (currentUser?.id !== user.id) return null;
      return user.phoneNumber;
    },

    // またはnullを返す代わりに例外を投げる
    privateData: (user, _, { currentUser }) => {
      if (currentUser?.id !== user.id) {
        throw new ForbiddenError('Not authorized');
      }
      return user.privateData;
    }
  }
};

Non-null フィールドの失敗が親全体をnullにする

重大度:MEDIUM

状況:便宜上フィールドをnon-nullにします。リゾルバーが例外を投げるか、nullを返します。エラーが親全体をnullにするまで、上位に伝播します。最終的にクエリ全体がnullまたはエラーで返されます。

兆候:

  • クエリが予期せずnullを返す
  • 1つのエラーが関係のないフィールドに影響
  • 部分的なデータを返せない

これが壊れる理由: GraphQLのnull伝播は、non-nullフィールドが解決できない場合、その親がnullになることを意味します。その親もnon-nullの場合、さらに伝播します。1つの失敗フィールドがレスポンス全体を破壊できます。

推奨される修正:

# Null可能性を意図的に設計

# 間違い:すべてnon-null
type User {
  id: ID!
  name: String!
  email: String!
  avatar: String!      # アバターがない場合はどうする?
  lastLogin: DateTime! # ログインしたことがない場合はどうする?
}

# 正しい:適切な場所でnullable
type User {
  id: ID!              # 常に存在
  name: String!        # 必須フィールド
  email: String!       # 必須フィールド
  avatar: String       # オプション - 存在しない可能性
  lastLogin: DateTime  # Nullable - nullの可能性
}

# リストの場合:
# [User!]! - Non-null リスト(推奨)
# [User!]  - Nullable リストのnon-null ユーザー
# [User]!  - Non-null リストのnullable ユーザー(まれ)
# [User]   - Nullable リストのnullable ユーザー(避ける)

ルール:

  • 常に存在し失敗がクエリを失敗させるべき場合はnon-null
  • オプションまたは失敗がレスポンスを壊さない場合はnullable

高価なクエリが安いクエリと同じように扱われる

重大度:MEDIUM

状況:すべてのクエリが同じように処理されます。シンプルな user(id) クエリと users(first: 1000) { posts { comments } } は同じリソースを使用します。高価なクエリが安いクエリを飢餓状態にします。

兆候:

  • 高価なクエリがすべてを遅くする
  • クエリを優先順位付けする方法がない
  • レート制限が効果的でない

これが壊れる理由: すべてのGraphQL操作が等しくありません。1000ユーザーをネストされたデータで取得することは、単一ユーザーを取得することより数桁高価です。コスト分析なしでは、正しくレート制限できません。

推奨される修正:

# クエリコスト分析

import { createComplexityLimitRule } from 'graphql-validation-complexity';

// フィールドごとに複雑度を定義
const complexityRules = createComplexityLimitRule(1000, {
  scalarCost: 1,
  objectCost: 10,
  listFactor: 10,
  // カスタムフィールドコスト
  fieldCost: {
    'Query.searchUsers': 100,
    'Query.analytics': 500,
    'User.posts': ({ args }) => args.limit || 10
  }
});

// レート制限用のコスト追跡
const costPlugin = {
  requestDidStart() {
    return {
      didResolveOperation({ request, document }) {
        const cost = calculateQueryCost(document);
        if (cost > 1000) {
          throw new Error(`Query too expensive: ${cost}`);
        }
        // レート制限用コストを追跡
        rateLimiter.consume(request.userId, cost);
      }
    };
  }
};

サブスクリプションが正しくクリーンアップされない

重大度:MEDIUM

状況:クライアントがサブスクライブしますが、きちんと購読解除しません。ネットワーク問題が孤立したサブスクリプションを残します。サーバーメモリが死んだサブスクリプションの蓄積につれて成長します。

兆候:

  • メモリ使用量が時間とともに増加
  • 死んだ接続が蓄積
  • サーバーが遅くなる

これが壊れる理由: 各サブスクリプションはサーバーリソースを保持します。切断時の適切なクリーンアップなしでは、リソースが蓄積されます。長時間実行されるサーバーは最終的にメモリが枯渇します。

推奨される修正:

# 適切なサブスクリプションクリーンアップ

import { PubSub, withFilter } from 'graphql-subscriptions';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';

const pubsub = new PubSub();

// アクティブなサブスクリプションを追跡
const activeSubscriptions = new Map();

const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql'
});

useServer({
  schema,
  context: (ctx) => ({
    pubsub,
    userId: ctx.connectionParams?.userId
  }),
  onConnect: (ctx) => {
    console.log('Client connected');
  },
  onDisconnect: (ctx) => {
    // この接続のリソースをクリーンアップ
    const userId = ctx.connectionParams?.userId;
    activeSubscriptions.delete(userId);
  }
}, wsServer);

// クリーンアップ付きサブスクリプションリゾルバー
Subscription: {
  messageReceived: {
    subscribe: withFilter(
      (_, { roomId }, { pubsub, userId }) => {
        // サブスクリプションを追跡
        activeSubscriptions.set(userId, roomId);
        return pubsub.asyncIterator(`ROOM_${roomId}`);
      },
      (payload, { roomId }) => {
        return payload.roomId === roomId;
      }
    )
  }
}

検証チェック

本番環境でイントロスペクションが有効

重大度:WARNING

メッセージ:本番環境ではイントロスペクションを無効にするべき

修正アクション:Set introspection: process.env.NODE_ENV !== 'production'

リゾルバーのダイレクトなデータベースクエリ

重大度:WARNING

メッセージ:DataLoaderを使用してクエリをバッチおよびキャッシュすることを検討してください

修正アクション:DataLoaderを作成し、直接クエリの代わりに .load() を使用

クエリ深度制限がない

重大度:WARNING

メッセージ:DoSを防止するために深度制限を追加することを検討してください

修正アクション:Add validationRules: [depthLimit(10)]

try-catchなしのリゾルバー

重大度:INFO

メッセージ:より良いエラーメッセージを提供するためにリゾルバーロジックをtry-catchでラップすることを検討してください

修正アクション:エラー処理をリゾルバーに追加

スキーマのJSONまたはAny型

重大度:INFO

メッセージ:GraphQLの型安全性を回避するJSONやAny型を避けてください

修正アクション:適切な入出力型を定義

ミューテーションが裸の型の代わりにペイロード型を返す

重大度:INFO

メッセージ:ミューテーションにはペイロード型を使用することを検討してください(エラーを含む)

修正アクション:ユーザーとエラーフィールド付きCreateUserPayload型を作成

ページネーション引数のないリストフィールド

重大度:INFO

メッセージ:リストフィールドはページネーション(limit、first、after)を持つべき

修正アクション:引数を追加:field(limit: Int, offset: Int): [Type!]!

エラー処理のないクエリフック

重大度:INFO

メッセージ:UIでクエリエラーを処理してください

修正アクション:デストラクチャーしてエラーを処理:const { error } = useQuery(...)

refetchの代わりにキャッシュ更新を使用しない

重大度:INFO

メッセージ:より良いUXのためにrefetchの代わりにキャッシュ更新を検討してください

修正アクション:キャッシュを直接変更する更新機能を使用

コラボレーション

デリゲーション トリガー

  • ユーザーがデータベース最適化を必要とする -> postgres-wizard(GraphQLリゾルバー用のクエリ最適化)
  • ユーザーが認証システムを必要とする -> authentication-oauth(GraphQLコンテキスト用の認証)
  • ユーザーがキャッシングレイヤーを必要とする -> caching-strategies(応答キャッシング、DataLoaderキャッシング)
  • ユーザーがリアルタイムインフラストラクチャを必要とする -> backend(サブスクリプション用のWebSocket設定)

関連スキル

相性が良い:backendpostgres-wizardnextjs-app-routerreact-patterns

使用するタイミング

  • ユーザーが言及または暗示:graphql
  • ユーザーが言及または暗示:graphql schema
  • ユーザーが言及または暗示:graphql resolver
  • ユーザーが言及または暗示:apollo server
  • ユーザーが言及または暗示:apollo client
  • ユーザーが言及または暗示:graphql federation
  • ユーザーが言及または暗示:dataloader
  • ユーザーが言及または暗示:graphql codegen
  • ユーザーが言及または暗示:graphql query
  • ユーザーが言及または暗示:graphql mutation

制限事項

  • このスキルは、上記で説明されているスコープと明確に一致するタスク場合にのみ使用してください。
  • 出力を環境固有の検証、テスト、または専門家のレビューの代替として扱わないでください。
  • 必要な入力、権限、安全境界、または成功基準が不明な場合は停止して説明を求めてください。

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
sickn33
リポジトリ
sickn33/antigravity-awesome-skills
ライセンス
MIT
最終更新
不明

Source: https://github.com/sickn33/antigravity-awesome-skills / ライセンス: MIT

関連スキル

OpenAIデータ・分析⭐ リポ 1,451

hugging-face-trackio

Trackioを使用してMLトレーニング実験を追跡・可視化できます。トレーニング中のメトリクスログ記録(Python API)、トレーニング診断のアラート発火、ログされたメトリクスの取得・分析(CLI)が必要な場合に活用してください。リアルタイムダッシュボード表示、Webhookを使用したアラート、HF Space同期、自動化向けのJSON出力に対応しています。

by gradio-app
汎用データ・分析⭐ リポ 855

btc-bottom-model

ビットコインのサイクルタイミングモデルで、加重スコアリングシステムを搭載しています。日次パルス(4指標、32ポイント)とウィークリー構造(9指標、68ポイント)の2カテゴリーにわたる13の指標を追跡し、0~100のマーケットヒートスコアを算出します。ETFフロー、ファンディングレート、ロング/ショート比率、恐怖・貪欲指数、LTH-MVRV、NUPL、SOPR(LTH+STH)、LTH供給率、移動平均倍率(365日MA、200週MA)、週次RSI、出来高トレンドに対応します。市場サイクル全体を通じて買いと売りの両方の推奨を提供します。ビットコインの底値拾い、BTCサイクルポジション、買い時・売り時、オンチェーン指標、MVRV、NUPL、SOPR、LTH動向、ETFの流出入、ファンディングレート、恐怖指数、ビットコインが過熱状態か、マイナーコスト、暗号資産市場のセンチメント、BTCのポジションサイジング、「今ビットコインを買うべきか」「BTCが天井をつけているか」「オンチェーン指標は何を示しているか」といった質問の際にこのスキルを活用します。

by star23
Anthropic Claudeデータ・分析⭐ リポ 380

protein_solubility_optimization

タンパク質の溶解性最適化 - タンパク質の溶解性を最適化します。タンパク質の特性を計算し、溶解性と親水性を予測し、有効な変異を提案します。タンパク質配列の特性計算、タンパク質機能の予測、親水性計算、ゼロショット配列予測を含むタンパク質エンジニアリング業務に使用できます。3つのSCPサーバーから4つのツールを統合しています。

by SpectrAI-Initiative
Anthropic Claudeデータ・分析⭐ リポ 1,743

research-lookup

Parallel Chat APIまたはPerplexity sonar-pro-searchを使用して、最新の研究情報を検索できます。学術論文の検索にも対応しています。クエリは自動的に最適なバックエンドにルーティングされるため、論文の検索、研究データの収集、科学情報の検証に活用できます。

by K-Dense-AI
Anthropic Claudeデータ・分析⭐ リポ 299

tree-formatting

ggtree(R)またはiTOL(ウェブ)を使用して、系統樹の可視化とフォーマットを行います。系統樹を図として描画する際、ツリーレイアウトの選択、分類学に基づく枝やラベルの色付け、クレードの折りたたみ、サポート値の表示、またはツリーへのオーバーレイ追加が必要な場合に使用してください。系統推定(protein-phylogenyスキルを使用)やドメイン注釈(今後の独立したスキル)には使用しないでください。

by majiayu000
汎用データ・分析⭐ リポ 145

querying-indonesian-gov-data

インドネシア政府の50以上のAPIとデータソースに接続できます。BPJPH(ハラール認証)、BOM(食品安全)、OJK(金融適正性)、BPS(統計)、BMKG(気象・地震)、インドネシア中央銀行(為替レート)、IDX(株式)、CKAN公開データポータル、pasal.id(第三者法MCP)に対応しています。インドネシア政府データを活用したアプリ開発、.go.idウェブサイトのスクレイピング、ハラール認証の確認、企業の法的適正性の検証、金融機関ステータスの照会、またはインドネシアMCPサーバーへの接続時に使用できます。CSRF処理、CKAN API使用方法、IP制限回避など、すぐに実行可能なPythonパターンを含んでいます。

by suryast
本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: sickn33 · sickn33/antigravity-awesome-skills · ライセンス: MIT