[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CX3ZJ46RD1JZHMWR1C85AV":3,"skill-same:01M6CX3ZJ46RD1JZHMWR1C85AV":58},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":24,"linkOnly":25,"markdownContent":26,"markdownBody":27,"frontMatter":28,"refName":32,"commitSha":33,"skillDirectory":5,"forksCount":34,"repositoryHtmlUrl":35,"repositoryHomepageUrl":6,"ownerHtmlUrl":36,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":37,"install":38,"resources":51},"01M6CX3ZJ46RD1JZHMWR1C85AV","grpc",null,"gRPC framework best practices and implementation","ffsshhttiikk\u002Fopencode-agents-skills","ffsshhttiikk","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F226346827?v=4","01M6CWD9RR3Y7VRQX3MXYC8FYH","01M6CWD9WVPYAZW0J82G1VKGHY",3,"MIT","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fgrpc\u002FSKILL.md",[],[5,18,19,20,21,22,23],"design","best practices","framework","api","opencode","implementation","2026-11-10 10:12:30",false,"---\nname: grpc\ndescription: gRPC framework best practices and implementation\nlicense: MIT\ncompatibility: opencode\nmetadata:\n  audience: developers\n  category: api-design\n---\n## What I do\n- Define Protocol Buffer schemas\n- Implement gRPC services\n- Handle streaming\n- Implement authentication\n- Handle errors properly\n- Manage versioning\n- Test gRPC services\n- Scale gRPC deployments\n\n## When to use me\nWhen implementing gRPC services or working with Protocol Buffers.\n\n## Protocol Buffer Definition\n```protobuf\nsyntax = \"proto3\";\n\npackage user.v1;\n\noption go_package = \"github.com\u002Fcompany\u002Fapi\u002Fuser\u002Fv1\";\n\nimport \"google\u002Fprotobuf\u002Ftimestamp.proto\";\nimport \"google\u002Fprotobuf\u002Fempty.proto\";\n\n\n\u002F\u002F Service definition\nservice UserService {\n  \u002F\u002F Unary RPC\n  rpc GetUser(GetUserRequest) returns (User);\n  \n  \u002F\u002F Server streaming\n  rpc ListUsers(ListUsersRequest) returns (stream User);\n  \n  \u002F\u002F Client streaming\n  rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);\n  \n  \u002F\u002F Bidirectional streaming\n  rpc StreamUserUpdates(stream UserUpdateRequest) returns (stream User);\n  \n  \u002F\u002F Health check\n  rpc HealthCheck(google.protobuf.Empty) returns (HealthCheckResponse);\n}\n\n\n\u002F\u002F Message types\nmessage GetUserRequest {\n  string user_id = 1;\n}\n\nmessage User {\n  string id = 1;\n  string email = 2;\n  string name = 3;\n  UserStatus status = 4;\n  google.protobuf.Timestamp created_at = 5;\n  google.protobuf.Timestamp updated_at = 6;\n}\n\nenum UserStatus {\n  USER_STATUS_UNSPECIFIED = 0;\n  USER_STATUS_ACTIVE = 1;\n  USER_STATUS_INACTIVE = 2;\n  USER_STATUS_SUSPENDED = 3;\n}\n\nmessage ListUsersRequest {\n  int32 page_size = 1;\n  string page_token = 2;\n  UserStatus status_filter = 3;\n}\n\nmessage CreateUserRequest {\n  string email = 1;\n  string name = 2;\n  string password = 3;\n}\n\nmessage CreateUsersResponse {\n  repeated User users = 1;\n  int32 failed_count = 2;\n}\n\nmessage UserUpdateRequest {\n  string user_id = 1;\n  string name = 2;\n}\n\nmessage HealthCheckResponse {\n  bool healthy = 1;\n  string version = 2;\n}\n```\n\n## Go gRPC Server\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"net\"\n\n\t\"google.golang.org\u002Fgrpc\"\n\t\"google.golang.org\u002Fgrpc\u002Fcodes\"\n\t\"google.golang.org\u002Fgrpc\u002Fcredentials\"\n\t\"google.golang.org\u002Fgrpc\u002Fmetadata\"\n\t\"google.golang.org\u002Fgrpc\u002Fstatus\"\n\t\"google.golang.org\u002Fprotobuf\u002Ftypes\u002Fknown\u002Ftimestamppb\"\n\n\tpb \"github.com\u002Fcompany\u002Fapi\u002Fuser\u002Fv1\"\n)\n\ntype UserServer struct {\n\tpb.UnimplementedUserServiceServer\n\tuserStore UserStore\n}\n\ntype UserStore interface {\n\tGetUser(ctx context.Context, id string) (*pb.User, error)\n\tListUsers(ctx context.Context, filter *pb.UserStatus, limit int) ([]*pb.User, error)\n\tCreateUser(ctx context.Context, user *pb.User) error\n}\n\nfunc NewUserServer(store UserStore) *UserServer {\n\treturn &UserServer{userStore: store}\n}\n\nfunc (s *UserServer) GetUser(\n\tctx context.Context,\n\treq *pb.GetUserRequest,\n) (*pb.User, error) {\n\t\u002F\u002F Extract metadata for logging\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif ok {\n\t\tlog.Printf(\"GetUser request: user_id=%s, from=%v\", req.UserId, md.Get(\"x-forwarded-for\"))\n\t}\n\n\t\u002F\u002F Validate request\n\tif req.UserId == \"\" {\n\t\treturn nil, status.Error(codes.InvalidArgument, \"user_id is required\")\n\t}\n\n\t\u002F\u002F Fetch user\n\tuser, err := s.userStore.GetUser(ctx, req.UserId)\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.NotFound, \"user not found: %s\", err)\n\t}\n\n\treturn user, nil\n}\n\nfunc (s *UserServer) ListUsers(\n\treq *pb.ListUsersRequest,\n\tstream pb.UserService_ListUsersServer,\n) error {\n\tctx := stream.Context()\n\n\t\u002F\u002F Parse pagination\n\tpageSize := int(req.PageSize)\n\tif pageSize \u003C= 0 {\n\t\tpageSize = 10\n\t}\n\tif pageSize > 100 {\n\t\tpageSize = 100\n\t}\n\n\tusers, err := s.userStore.ListUsers(ctx, req.StatusFilter, pageSize)\n\tif err != nil {\n\t\treturn status.Errorf(codes.Internal, \"failed to list users: %v\", err)\n\t}\n\n\t\u002F\u002F Stream users to client\n\tfor _, user := range users {\n\t\tif err := stream.Send(user); err != nil {\n\t\t\treturn status.Errorf(codes.Internal, \"failed to send user: %v\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *UserService) CreateUsers(\n\tstream pb.UserService_CreateUsersServer,\n) error {\n\tctx := stream.Context()\n\tvar users []*pb.User\n\tvar failedCount int32\n\n\tfor {\n\t\treq, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tif err.Error() == \"EOF\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn status.Errorf(codes.Internal, \"failed to receive request: %v\", err)\n\t\t}\n\n\t\tuser := &pb.User{\n\t\t\tEmail:     req.Email,\n\t\t\tName:      req.Name,\n\t\t\tStatus:    pb.UserStatus_USER_STATUS_ACTIVE,\n\t\t\tCreatedAt: timestamppb.Now(),\n\t\t}\n\n\t\tif err := s.userStore.CreateUser(ctx, user); err != nil {\n\t\t\tfailedCount++\n\t\t\tlog.Printf(\"Failed to create user: %v\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tusers = append(users, user)\n\t}\n\n\treturn stream.SendAndClose(&pb.CreateUsersResponse{\n\t\tUsers:       users,\n\t\tFailedCount: failedCount,\n\t})\n}\n\nfunc (s *UserServer) StreamUserUpdates(\n\tstream pb.UserService_StreamUserUpdatesServer,\n) error {\n\tctx := stream.Context()\n\n\tfor {\n\t\tselect {\n\t\tcase \u003C-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tdefault:\n\t\t\treq, err := stream.Recv()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tuser, err := s.userStore.GetUser(ctx, req.UserId)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err := stream.Send(user); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n}\n\n\u002F\u002F Server reflection for debugging\nfunc init() {\n\t\u002F\u002F Enable reflection\n\t\u002F\u002F reflection.Register(grpcServer)\n}\n```\n\n## gRPC Client\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"google.golang.org\u002Fgrpc\"\n\t\"google.golang.org\u002Fgrpc\u002Fcredentials\u002Finsecure\"\n\t\"google.golang.org\u002Fgrpc\u002Fmetadata\"\n\n\tpb \"github.com\u002Fcompany\u002Fapi\u002Fuser\u002Fv1\"\n)\n\ntype UserClient struct {\n\tconn   *grpc.ClientConn\n\tclient pb.UserServiceClient\n}\n\nfunc NewUserClient(addr string) (*UserClient, error) {\n\tconn, err := grpc.Dial(\n\t\taddr,\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\tgrpc.WithUnaryInterceptor(loggingInterceptor),\n\t\tgrpc.WithStreamInterceptor(streamLoggingInterceptor),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &UserClient{\n\t\tconn:   conn,\n\t\tclient: pb.NewUserServiceClient(conn),\n\t}, nil\n}\n\nfunc (c *UserClient) GetUser(ctx context.Context, userId string) (*pb.User, error) {\n\t\u002F\u002F Add metadata for tracing\n\tctx = metadata.AppendToOutgoingContext(ctx, \"x-request-id\", \"req-123\")\n\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\n\treturn c.client.GetUser(ctx, &pb.GetUserRequest{\n\t\tUserId: userId,\n\t})\n}\n\nfunc (c *UserClient) ListActiveUsers(ctx context.Context) ([]*pb.User, error) {\n\tctx, cancel := context.WithTimeout(ctx, 30*time.Second)\n\tdefer cancel()\n\n\tstream, err := c.client.ListUsers(ctx, &pb.ListUsersRequest{\n\t\tPageSize:  100,\n\t\tStatusFilter: pb.UserStatus_USER_STATUS_ACTIVE,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar users []*pb.User\n\tfor {\n\t\tuser, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tif err.Error() == \"EOF\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tusers = append(users, user)\n\t}\n\n\treturn users, nil\n}\n\nfunc (c *UserClient) Close() error {\n\treturn c.conn.Close()\n}\n\n\u002F\u002F Interceptors\nfunc loggingInterceptor(\n\tctx context.Context,\n\tmethod string,\n\treq, reply interface{},\n\tcc *grpc.ClientConn,\n\tinvoker grpc.UnaryInvoker,\n\topts ...grpc.CallOption,\n) error {\n\tstart := time.Now()\n\t\n\terr := invoker(ctx, method, req, reply, cc, opts...)\n\t\n\tlog.Printf(\"gRPC call: %s, duration: %v, error: %v\",\n\t\tmethod, time.Since(start), err)\n\t\n\treturn err\n}\n\nfunc streamLoggingInterceptor(\n\tctx context.Context,\n\tdesc *grpc.StreamDesc,\n\tcc *grpc.ClientConn,\n\tmethod string,\n\tstream grpc.ClientStream,\n\topts ...grpc.CallOption,\n) error {\n\tstart := time.Now()\n\t\n\terr := stream.RecvMsg(nil)\n\t\n\tlog.Printf(\"gRPC stream: %s, duration: %v, error: %v\",\n\t\tmethod, time.Since(start), err)\n\t\n\treturn err\n}\n```\n\n## Error Handling\n```go\npackage status\n\nimport (\n\t\"google.golang.org\u002Fgrpc\u002Fcodes\"\n\t\"google.golang.org\u002Fgrpc\u002Fstatus\"\n)\n\n\u002F\u002F Custom error codes\nconst (\n\tErrCodeUserNotFound = \"USER_NOT_FOUND\"\n\tErrCodeInvalidInput = \"INVALID_INPUT\"\n\tErrCodeUnauthorized = \"UNAUTHORIZED\"\n)\n\nfunc UserNotFound(id string) error {\n\treturn status.Errorf(codes.NotFound, \"user not found: %s\", id)\n}\n\nfunc InvalidInput(field, reason string) error {\n\treturn status.Errorf(codes.InvalidArgument, \"invalid %s: %s\", field, reason)\n}\n\nfunc Unauthorized(reason string) error {\n\treturn status.Errorf(codes.Unauthenticated, \"unauthorized: %s\", reason)\n}\n```\n\n## gRPC Best Practices\n```\n1. Use Protocol Buffers for schema\n   - Define contracts explicitly\n   - Enable backwards compatibility\n\n2. Handle errors properly\n   - Use gRPC status codes\n   - Include error details\n\n3. Implement interceptors\n   - Logging, auth, metrics\n\n4. Use streaming appropriately\n   - Server streaming for lists\n   - Bidirectional for real-time\n\n5. Set timeouts\n   - Always use context with timeout\n\n6. Enable reflection\n   - For debugging and tools\n\n7. Version your APIs\n   - Include version in package name\n   - Support multiple versions\n\n8. Use proper authentication\n   - Token-based or mTLS\n\n9. Monitor and trace\n   - Add interceptors for metrics\n   - Use OpenTelemetry\n\n10. Test thoroughly\n    - Unit tests with mock stores\n    - Integration tests\n```\n","## What I do\n- Define Protocol Buffer schemas\n- Implement gRPC services\n- Handle streaming\n- Implement authentication\n- Handle errors properly\n- Manage versioning\n- Test gRPC services\n- Scale gRPC deployments\n\n## When to use me\nWhen implementing gRPC services or working with Protocol Buffers.\n\n## Protocol Buffer Definition\n```protobuf\nsyntax = \"proto3\";\n\npackage user.v1;\n\noption go_package = \"github.com\u002Fcompany\u002Fapi\u002Fuser\u002Fv1\";\n\nimport \"google\u002Fprotobuf\u002Ftimestamp.proto\";\nimport \"google\u002Fprotobuf\u002Fempty.proto\";\n\n\n\u002F\u002F Service definition\nservice UserService {\n  \u002F\u002F Unary RPC\n  rpc GetUser(GetUserRequest) returns (User);\n  \n  \u002F\u002F Server streaming\n  rpc ListUsers(ListUsersRequest) returns (stream User);\n  \n  \u002F\u002F Client streaming\n  rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);\n  \n  \u002F\u002F Bidirectional streaming\n  rpc StreamUserUpdates(stream UserUpdateRequest) returns (stream User);\n  \n  \u002F\u002F Health check\n  rpc HealthCheck(google.protobuf.Empty) returns (HealthCheckResponse);\n}\n\n\n\u002F\u002F Message types\nmessage GetUserRequest {\n  string user_id = 1;\n}\n\nmessage User {\n  string id = 1;\n  string email = 2;\n  string name = 3;\n  UserStatus status = 4;\n  google.protobuf.Timestamp created_at = 5;\n  google.protobuf.Timestamp updated_at = 6;\n}\n\nenum UserStatus {\n  USER_STATUS_UNSPECIFIED = 0;\n  USER_STATUS_ACTIVE = 1;\n  USER_STATUS_INACTIVE = 2;\n  USER_STATUS_SUSPENDED = 3;\n}\n\nmessage ListUsersRequest {\n  int32 page_size = 1;\n  string page_token = 2;\n  UserStatus status_filter = 3;\n}\n\nmessage CreateUserRequest {\n  string email = 1;\n  string name = 2;\n  string password = 3;\n}\n\nmessage CreateUsersResponse {\n  repeated User users = 1;\n  int32 failed_count = 2;\n}\n\nmessage UserUpdateRequest {\n  string user_id = 1;\n  string name = 2;\n}\n\nmessage HealthCheckResponse {\n  bool healthy = 1;\n  string version = 2;\n}\n```\n\n## Go gRPC Server\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"net\"\n\n\t\"google.golang.org\u002Fgrpc\"\n\t\"google.golang.org\u002Fgrpc\u002Fcodes\"\n\t\"google.golang.org\u002Fgrpc\u002Fcredentials\"\n\t\"google.golang.org\u002Fgrpc\u002Fmetadata\"\n\t\"google.golang.org\u002Fgrpc\u002Fstatus\"\n\t\"google.golang.org\u002Fprotobuf\u002Ftypes\u002Fknown\u002Ftimestamppb\"\n\n\tpb \"github.com\u002Fcompany\u002Fapi\u002Fuser\u002Fv1\"\n)\n\ntype UserServer struct {\n\tpb.UnimplementedUserServiceServer\n\tuserStore UserStore\n}\n\ntype UserStore interface {\n\tGetUser(ctx context.Context, id string) (*pb.User, error)\n\tListUsers(ctx context.Context, filter *pb.UserStatus, limit int) ([]*pb.User, error)\n\tCreateUser(ctx context.Context, user *pb.User) error\n}\n\nfunc NewUserServer(store UserStore) *UserServer {\n\treturn &UserServer{userStore: store}\n}\n\nfunc (s *UserServer) GetUser(\n\tctx context.Context,\n\treq *pb.GetUserRequest,\n) (*pb.User, error) {\n\t\u002F\u002F Extract metadata for logging\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif ok {\n\t\tlog.Printf(\"GetUser request: user_id=%s, from=%v\", req.UserId, md.Get(\"x-forwarded-for\"))\n\t}\n\n\t\u002F\u002F Validate request\n\tif req.UserId == \"\" {\n\t\treturn nil, status.Error(codes.InvalidArgument, \"user_id is required\")\n\t}\n\n\t\u002F\u002F Fetch user\n\tuser, err := s.userStore.GetUser(ctx, req.UserId)\n\tif err != nil {\n\t\treturn nil, status.Errorf(codes.NotFound, \"user not found: %s\", err)\n\t}\n\n\treturn user, nil\n}\n\nfunc (s *UserServer) ListUsers(\n\treq *pb.ListUsersRequest,\n\tstream pb.UserService_ListUsersServer,\n) error {\n\tctx := stream.Context()\n\n\t\u002F\u002F Parse pagination\n\tpageSize := int(req.PageSize)\n\tif pageSize \u003C= 0 {\n\t\tpageSize = 10\n\t}\n\tif pageSize > 100 {\n\t\tpageSize = 100\n\t}\n\n\tusers, err := s.userStore.ListUsers(ctx, req.StatusFilter, pageSize)\n\tif err != nil {\n\t\treturn status.Errorf(codes.Internal, \"failed to list users: %v\", err)\n\t}\n\n\t\u002F\u002F Stream users to client\n\tfor _, user := range users {\n\t\tif err := stream.Send(user); err != nil {\n\t\t\treturn status.Errorf(codes.Internal, \"failed to send user: %v\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *UserService) CreateUsers(\n\tstream pb.UserService_CreateUsersServer,\n) error {\n\tctx := stream.Context()\n\tvar users []*pb.User\n\tvar failedCount int32\n\n\tfor {\n\t\treq, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tif err.Error() == \"EOF\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn status.Errorf(codes.Internal, \"failed to receive request: %v\", err)\n\t\t}\n\n\t\tuser := &pb.User{\n\t\t\tEmail:     req.Email,\n\t\t\tName:      req.Name,\n\t\t\tStatus:    pb.UserStatus_USER_STATUS_ACTIVE,\n\t\t\tCreatedAt: timestamppb.Now(),\n\t\t}\n\n\t\tif err := s.userStore.CreateUser(ctx, user); err != nil {\n\t\t\tfailedCount++\n\t\t\tlog.Printf(\"Failed to create user: %v\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tusers = append(users, user)\n\t}\n\n\treturn stream.SendAndClose(&pb.CreateUsersResponse{\n\t\tUsers:       users,\n\t\tFailedCount: failedCount,\n\t})\n}\n\nfunc (s *UserServer) StreamUserUpdates(\n\tstream pb.UserService_StreamUserUpdatesServer,\n) error {\n\tctx := stream.Context()\n\n\tfor {\n\t\tselect {\n\t\tcase \u003C-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tdefault:\n\t\t\treq, err := stream.Recv()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tuser, err := s.userStore.GetUser(ctx, req.UserId)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err := stream.Send(user); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n}\n\n\u002F\u002F Server reflection for debugging\nfunc init() {\n\t\u002F\u002F Enable reflection\n\t\u002F\u002F reflection.Register(grpcServer)\n}\n```\n\n## gRPC Client\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"google.golang.org\u002Fgrpc\"\n\t\"google.golang.org\u002Fgrpc\u002Fcredentials\u002Finsecure\"\n\t\"google.golang.org\u002Fgrpc\u002Fmetadata\"\n\n\tpb \"github.com\u002Fcompany\u002Fapi\u002Fuser\u002Fv1\"\n)\n\ntype UserClient struct {\n\tconn   *grpc.ClientConn\n\tclient pb.UserServiceClient\n}\n\nfunc NewUserClient(addr string) (*UserClient, error) {\n\tconn, err := grpc.Dial(\n\t\taddr,\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\tgrpc.WithUnaryInterceptor(loggingInterceptor),\n\t\tgrpc.WithStreamInterceptor(streamLoggingInterceptor),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &UserClient{\n\t\tconn:   conn,\n\t\tclient: pb.NewUserServiceClient(conn),\n\t}, nil\n}\n\nfunc (c *UserClient) GetUser(ctx context.Context, userId string) (*pb.User, error) {\n\t\u002F\u002F Add metadata for tracing\n\tctx = metadata.AppendToOutgoingContext(ctx, \"x-request-id\", \"req-123\")\n\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\n\treturn c.client.GetUser(ctx, &pb.GetUserRequest{\n\t\tUserId: userId,\n\t})\n}\n\nfunc (c *UserClient) ListActiveUsers(ctx context.Context) ([]*pb.User, error) {\n\tctx, cancel := context.WithTimeout(ctx, 30*time.Second)\n\tdefer cancel()\n\n\tstream, err := c.client.ListUsers(ctx, &pb.ListUsersRequest{\n\t\tPageSize:  100,\n\t\tStatusFilter: pb.UserStatus_USER_STATUS_ACTIVE,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar users []*pb.User\n\tfor {\n\t\tuser, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tif err.Error() == \"EOF\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tusers = append(users, user)\n\t}\n\n\treturn users, nil\n}\n\nfunc (c *UserClient) Close() error {\n\treturn c.conn.Close()\n}\n\n\u002F\u002F Interceptors\nfunc loggingInterceptor(\n\tctx context.Context,\n\tmethod string,\n\treq, reply interface{},\n\tcc *grpc.ClientConn,\n\tinvoker grpc.UnaryInvoker,\n\topts ...grpc.CallOption,\n) error {\n\tstart := time.Now()\n\t\n\terr := invoker(ctx, method, req, reply, cc, opts...)\n\t\n\tlog.Printf(\"gRPC call: %s, duration: %v, error: %v\",\n\t\tmethod, time.Since(start), err)\n\t\n\treturn err\n}\n\nfunc streamLoggingInterceptor(\n\tctx context.Context,\n\tdesc *grpc.StreamDesc,\n\tcc *grpc.ClientConn,\n\tmethod string,\n\tstream grpc.ClientStream,\n\topts ...grpc.CallOption,\n) error {\n\tstart := time.Now()\n\t\n\terr := stream.RecvMsg(nil)\n\t\n\tlog.Printf(\"gRPC stream: %s, duration: %v, error: %v\",\n\t\tmethod, time.Since(start), err)\n\t\n\treturn err\n}\n```\n\n## Error Handling\n```go\npackage status\n\nimport (\n\t\"google.golang.org\u002Fgrpc\u002Fcodes\"\n\t\"google.golang.org\u002Fgrpc\u002Fstatus\"\n)\n\n\u002F\u002F Custom error codes\nconst (\n\tErrCodeUserNotFound = \"USER_NOT_FOUND\"\n\tErrCodeInvalidInput = \"INVALID_INPUT\"\n\tErrCodeUnauthorized = \"UNAUTHORIZED\"\n)\n\nfunc UserNotFound(id string) error {\n\treturn status.Errorf(codes.NotFound, \"user not found: %s\", id)\n}\n\nfunc InvalidInput(field, reason string) error {\n\treturn status.Errorf(codes.InvalidArgument, \"invalid %s: %s\", field, reason)\n}\n\nfunc Unauthorized(reason string) error {\n\treturn status.Errorf(codes.Unauthenticated, \"unauthorized: %s\", reason)\n}\n```\n\n## gRPC Best Practices\n```\n1. Use Protocol Buffers for schema\n   - Define contracts explicitly\n   - Enable backwards compatibility\n\n2. Handle errors properly\n   - Use gRPC status codes\n   - Include error details\n\n3. Implement interceptors\n   - Logging, auth, metrics\n\n4. Use streaming appropriately\n   - Server streaming for lists\n   - Bidirectional for real-time\n\n5. Set timeouts\n   - Always use context with timeout\n\n6. Enable reflection\n   - For debugging and tools\n\n7. Version your APIs\n   - Include version in package name\n   - Support multiple versions\n\n8. Use proper authentication\n   - Token-based or mTLS\n\n9. Monitor and trace\n   - Add interceptors for metrics\n   - Use OpenTelemetry\n\n10. Test thoroughly\n    - Unit tests with mock stores\n    - Integration tests\n```\n",{"name":5,"license":14,"metadata":29,"description":7,"compatibility":22},{"audience":30,"category":31},"developers","api-design","main","5f7a3e453c98261f93d19a6a7de44ad8f7b72d41",2,"https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk","2026-03-01 06:55:40",{"source":8,"skillName":5,"commands":39,"prompt":49,"zipAvailable":50},[40,43,46],{"tool":41,"command":42},"npx","npx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill grpc",{"tool":44,"command":45},"bunx","bunx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill grpc",{"tool":47,"command":48},"pnpm","pnpm dlx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill grpc","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fgrpc\u002FSKILL.md",true,[52],{"path":53,"resourceType":54,"resourceTypeDesc":55,"sizeBytes":56,"sourceHtmlUrl":57},"references\u002Fot-safe-assessment.md","REFERENCE","参考文档",411,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSecurity-and-Compliance\u002Freverse-skill-router\u002Fot-ics\u002Freferences\u002Fot-safe-assessment.md",[59,70,80,89,99,108],{"skillNo":60,"name":61,"nameZh":6,"description":62,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":24},"01M6CXT9TXDF79BWJTMQAPB49G","xamarin","Xamarin cross-platform development","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fxamarin\u002FSKILL.md",[],[61,66,67,68,22,69],"platform","cross-platform","mobile","development",{"skillNo":71,"name":72,"nameZh":6,"description":73,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":74,"categories":75,"keywords":76,"publishedAt":24},"01M6CXT8AD04MFYDA32VW3FKAG","wrangling","Data wrangling fundamentals","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwrangling\u002FSKILL.md",[],[72,77,22,78,79],"data wrangling","science","data",{"skillNo":81,"name":82,"nameZh":6,"description":83,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":84,"categories":85,"keywords":86,"publishedAt":24},"01M6CXT717GCN7SRRNPBDP4508","windows","Windows operating system administration and scripting","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwindows\u002FSKILL.md",[],[82,87,22,88],"system","scripting",{"skillNo":90,"name":91,"nameZh":6,"description":92,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":93,"categories":94,"keywords":95,"publishedAt":24},"01M6CXT54K794NMEM3XAPZZDKT","wide-column","Wide-column data model","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwide-column\u002FSKILL.md",[],[96,97,22,98,79],"wide column","databases","model",{"skillNo":100,"name":101,"nameZh":6,"description":102,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":105,"publishedAt":24},"01M6CXT28FMT2P4V9GZE67WA8E","wide-column-stores","Wide-column database stores","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwide-column-stores\u002FSKILL.md",[],[106,97,22,107],"wide column stores","database",{"skillNo":109,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":114,"publishedAt":24},"01M6CXT0D0M0Q4T2N7C2RX7DSN","websockets","WebSocket implementation and best practices","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwebsockets\u002FSKILL.md",[],[110,115,116,117,22,19,23],"websocket","real-time","time"]