--- title: "Meetup API Schema Introspection" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Meetup API Schema Introspection} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r} #| include: false #| label: setup knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = TRUE ) vcr::setup_knitr(prefix = "introsp-") meetupr:::mock_if_no_auth() meetupr::local_meetupr_debug(0) ``` ```{r} #| label: load library(meetupr) library(dplyr) ``` ## Introduction to GraphQL Introspection GraphQL APIs are self-documenting through a feature called **introspection**. This means you can query the API to learn about its own structure: what types exist, what fields are available on each type, and what operations you can perform. The meetupr package exposes this introspection capability through a family of `meetupr_schema_*` functions. These functions help you discover the full capabilities of the Meetup API beyond what the package's built-in wrapper functions provide. ### Why Use Introspection? Introspection is valuable when you need to: - **Discover new fields**: Meetup may add new data fields that aren't yet wrapped in meetupr functions - **Build custom queries**: Access specific field combinations not available through standard wrappers - **Understand relationships**: See how types connect (e.g., how Events relate to Groups and Venues) - **Check deprecations**: Identify deprecated fields before they're removed - **Explore Pro features**: Discover fields only available with Meetup Pro accounts ### How GraphQL Schema Works A GraphQL schema defines: 1. **Types**: Data structures like `Event`, `Group`, `Member` (think of these as classes or tables) 2. **Fields**: Properties on each type (like `title`, `dateTime`, `memberCount`) 3. **Queries**: Entry points for reading data (like `event(id: "123")`) 4. **Mutations**: Operations for modifying data (like `createEvent`, `updateGroup`) The schema also specifies: - Field types (String, Int, Boolean, or custom types) - Required vs. optional fields - Arguments each field accepts - Relationships between types (one-to-one, one-to-many) ## Recommended Exploration Workflow 1. **Cache the schema**: `schema <- meetupr_schema()` 2. **Browse queries**: `meetupr_schema_queries(schema)` to see entry points 3. **Search for types**: `meetupr_schema_search("topic", schema)` to find relevant types 4. **Examine type fields**: `meetupr_schema_type("TypeName", schema)` for details 5. **Check related types**: Follow object-type fields to discover relationships 6. **Build query**: Combine fields based on introspection data 7. **Test in Playground**: Validate query at 8. **Execute in R**: `meetupr_query()` with your final query 9. **Extract data**: Use purrr or dplyr to transform nested results This systematic approach helps you efficiently discover and utilize the full Meetup API schema for custom data extraction beyond the package's built-in functions. ## The meetupr_schema_* Function Family meetupr provides five introspection functions, all starting with `meetupr_schema_`: ```r meetupr_schema() # Get the complete schema meetupr_schema_queries() # List available query entry points meetupr_schema_mutations() # List available mutations meetupr_schema_search() # Search for types by name/description meetupr_schema_type() # Examine fields on a specific type ``` Type `meetupr_schema_` in your console and press Tab to see all available functions. ### Performance Tip: Cache the Schema The schema is large (~1-2MB) and rarely changes. Fetch it once and pass it to subsequent functions: ```{r} #| label: cache-schema #| cassette: true # Fetch once schema <- meetupr_schema() # Reuse for multiple queries (no additional API calls) queries <- meetupr_schema_queries(schema = schema) mutations <- meetupr_schema_mutations(schema = schema) event_fields <- meetupr_schema_type("Event", schema = schema) ``` This pattern avoids repeated introspection queries and speeds up exploration significantly. ## Step-by-Step Exploration Workflow Here's the recommended workflow for discovering and using new API capabilities: ### Step 1: Identify Available Entry Points Start by seeing what top-level queries exist: ```{r} #| label: query-fields query_fields <- meetupr_schema_queries(schema = schema) query_fields ``` Each row represents a query you can execute: - `field_name`: The query name (use this in your GraphQL query) - `description`: What the query does - `args_count`: How many arguments it accepts - `return_type`: What type of data it returns For example, if you see `groupByUrlname` returning `Group`, you know you can query group data using a URL name. **Finding specific queries**: ```{r} #| label: filter-queries # Find all group-related queries query_fields |> filter(grepl("group", field_name, ignore.case = TRUE)) # Find queries that don't require arguments query_fields |> filter(args_count == 0) ``` ### Step 2: Search for Relevant Types Once you know what queries exist, search for related data types: ```{r} #| label: search-types # Find all event-related types event_types <- meetupr_schema_search("event", schema = schema) event_types ``` The search looks in both type names and descriptions, so you might find: - `Event`: The main event type - `EventConnection`: Pagination wrapper for event lists - `EventEdge`: Individual event in a paginated list - `EventInput`: Input type for creating/updating events - `EventStatus`: Enum of possible event statuses **Key fields explained**: - `type_name`: The GraphQL type name - `kind`: Type category (OBJECT, ENUM, INTERFACE, INPUT_OBJECT, etc.) - `field_count`: How many fields the type has (0 for enums and inputs) **Different type kinds**: - `OBJECT`: Standard data type with fields (like Event, Group) - `ENUM`: Fixed set of values (like EventStatus: UPCOMING, PAST, CANCELLED) - `INPUT_OBJECT`: Type used as mutation argument (like CreateEventInput) - `INTERFACE`: Shared fields across multiple types - `SCALAR`: Primitive values (String, Int, Boolean, ID, DateTime) ```{r} #| label: search-users # Find user/member related types user_types <- meetupr_schema_search("user", schema = schema) user_types # Find location-related types location_types <- meetupr_schema_search("location", schema = schema) location_types ``` ### Step 3: Examine Type Fields Now inspect what fields a specific type has: ```{r} #| label: event-fields event_fields <- meetupr_schema_type("Event", schema = schema) event_fields ``` When multiple types match your search, use regex to select one: ```{r} # Use regular expression to choose one specific type event <- meetupr_schema_type("^Event$", schema = schema) event ``` This shows every field available on the `Event` type: - `field_name`: Field to request in your query - `description`: What the field represents - `type`: GraphQL type (String, Int, or another object type) - `deprecated`: Whether the field is being phased out **Understanding field types**: ```{r} #| label: examine-field-types # Look at specific field types event |> select(field_name, type) |> head(10) ``` Field types tell you: - Simple types (`String`, `Int`, `Boolean`): Return scalar values - Object types (`Group`, `Venue`): Return nested objects (you can query their fields too) - `ID`: Unique identifier (usually a string) **Find deprecated fields** (avoid using these): ```{r} #| label: deprecated-fields event |> filter(deprecated == TRUE) ``` **Explore complex types**: ```{r} #| label: group-fields # See what fields are on Group objects group_fields <- meetupr_schema_type("^Group$", schema = schema) group_fields ``` If an Event has a `group` field of type `Group`, you can query Group fields nested under Event: ```graphql query { event(id: "123") { title group { name urlname memberCount } } } ``` ### Step 4: Build Your Custom Query Armed with introspection data, construct a GraphQL query: ```{r} #| label: custom-build custom_query <- " query GetEventDetails($eventId: ID!) { event(id: $eventId) { id title description dateTime duration # Nested group information group { name urlname city } venues { name address city state postalCode country lat lon venueType } } } " ``` **Query anatomy**: - **Operation type**: `query` (or `mutation`) - **Operation name**: `GetEventDetails` (optional but helpful for debugging) - **Variables**: `$eventId: ID!` (the `!` means required) - **Field selection**: Choose which fields to retrieve - **Nested selections**: Query fields on related objects (group) **How to choose fields**: 1. Use `meetupr_schema_type()` to see available fields 2. Include scalar fields (String, Int, etc.) directly 3. For object fields (Group, Venue), nest another selection set 4. Test in the [Meetup API Playground](https://www.meetup.com/api/playground) first ### Step 5: Execute Your Query Run the query with `meetupr_query()`: ```{r} #| label: custom-query #| cassette: true result <- meetupr_query(custom_query, eventId = "103349942") result ``` The result structure mirrors your query structure: - `data`: Top-level wrapper - `event`: The query field you requested - Nested fields match your selection set **Extract specific data**: ```{r} #| label: extract-data # Get just the event title result$data$event$title # Get group information result$data$event$group # Get venue coordinates venue <- result$data$event$venues[[1]] c(lat = venue$lat, lng = venue$lon) ``` ## Working with Mutations Mutations modify data on the server (create, update, delete operations). They require appropriate permissions based on your Meetup account and group roles. ### Discovering Available Mutations ```{r} #| label: mutations mutations <- meetupr_schema_mutations(schema = schema) mutations ``` Each mutation: - Takes an `input` argument (usually an INPUT_OBJECT type) - Returns a payload type with `errors` field and the modified object - Requires authentication and appropriate permissions **Common mutation patterns**: - **Create**: `createEvent`, `createGroup` - **Update**: `updateEvent`, `updateMember` - **Delete**: `deleteEvent`, `removeGroupMember` - **RSVP**: `createEventRsvp`, `deleteEventRsvp` ### Understanding Mutation Structure Mutations follow a standard pattern in the Meetup API: ```{r} #| label: mutation-anatomy mutation_query <- " mutation OperationName($input: InputType!) { mutationField(input: $input) { # Always check for errors errors { code message } # The modified object (if successful) resultField { id # other fields you want back } } } " ``` **Mutation response structure**: 1. **errors**: Array of error objects (null if successful) 2. **Result field**: The created/updated object (null if errors occurred) Always check the `errors` field before accessing the result. ### Example: RSVP to an Event ```{r} #| label: mutation-example #| eval: false # First, explore the mutation mutations |> filter(field_name == "createEventRsvp") # Check what input fields are needed rsvp_input <- meetupr_schema_type("CreateEventRsvpInput", schema = schema) rsvp_input # Build the mutation rsvp_mutation <- " mutation RSVPToEvent($input: CreateEventRsvpInput!) { createEventRsvp(input: $input) { errors { code message } rsvp { id response created } } } " # Execute (requires authentication and valid event) result <- meetupr_query( rsvp_mutation, input = list( eventId = "123456", response = "YES" ) ) # Check for errors if (!is.null(result$data$createEventRsvp$errors)) { cli::cli_alert_danger("RSVP failed") print(result$data$createEventRsvp$errors) } else { cli::cli_alert_success("RSVP successful") print(result$data$createEventRsvp$rsvp) } ``` **Important mutation considerations**: - **Permissions**: You must have appropriate group roles - **Validation**: Input must match schema requirements exactly - **Idempotency**: Some mutations can be repeated safely, others cannot - **Rate limits**: Mutations count toward API rate limits (500 req/60s) ## Advanced Introspection Patterns ### Exploring Enum Values Enums are fixed sets of allowed values (like event status: UPCOMING, PAST, CANCELLED): ```{r} #| label: find-enums # Find all enum types enum_types <- schema$types[sapply(schema$types, function(x) { x$kind == "ENUM" })] # Example: Event status values event_status <- enum_types[sapply(enum_types, function(x) { x$name == "EventStatus" })][[1]] # Get allowed values sapply(event_status$enumValues, function(x) x$name) ``` This tells you exactly which status values are valid when filtering events. ### Finding Required vs. Optional Fields Field types can be wrapped in modifiers: - `!` suffix means **required** (NON_NULL kind) - `[]` brackets mean **list/array** (LIST kind) ```{r} #| label: required-fields # Find required fields on Event event |> filter(grepl("!", type)) |> select(field_name, type) ``` The `!` indicates you must provide this field in mutations or it will always be present in queries. ### Discovering Input Types for Mutations Input types define what data mutations accept: ```{r} #| label: input-types # Find all input types input_types <- meetupr_schema_search("Input", schema = schema) |> filter(kind == "INPUT_OBJECT") input_types ``` Examine specific input types to see required fields: ```{r} #| label: examine-input # See what fields CreateEventInput requires create_event_input <- meetupr_schema_type("CreateEventInput", schema = schema) create_event_input ``` ### Understanding Pagination Types Meetup uses cursor-based pagination with Connection/Edge patterns: ```{r} #| label: pagination-types # Find pagination-related types pagination_types <- meetupr_schema_search("Connection", schema = schema) pagination_types ``` **Pagination pattern**: ```graphql { group(urlname: "rladies-lagos") { upcomingEvents(input: {first: 10, after: "cursor"}) { pageInfo { hasNextPage endCursor } edges { node { # Event fields here } } } } } ``` - `pageInfo`: Contains `hasNextPage` (boolean) and `endCursor` (string) - `edges`: Array of results - `node`: The actual object (Event, Group, etc.) The meetupr package handles pagination automatically in wrapper functions, but understanding this structure helps when building custom queries. ## Exporting Schema for External Tools Some developers prefer graphical schema explorers or IDE plugins. Export the schema as JSON: ```{r} #| label: export-schema #| eval: false # Export full schema schema_json <- meetupr_schema(asis = TRUE) writeLines(schema_json, "meetupr_schema.json") ``` This JSON can be imported into: - [GraphQL Voyager](https://apis.guru/graphql-voyager/) for visual exploration - IDE plugins (VS Code GraphQL extension, IntelliJ GraphQL plugin) ## Practical Tips and Workflow ### Use the Meetup API Playground The [Meetup API Playground](https://www.meetup.com/api/playground) provides: - Autocomplete for fields and types - Real-time validation - Example queries - Schema documentation browser Build and test your query there, then copy it into `meetupr_query()`. ### Use Debug Mode Enable debug mode to see the exact GraphQL being sent: ```{r} #| label: debug-mode #| eval: false meetupr::local_meetupr_debug(TRUE) # Your query here result <- meetupr_query(custom_query, eventId = "123") meetupr::local_meetupr_debug(FALSE) ``` This prints: - The complete query with variables substituted - Request headers - Response structure ## Next Steps - **Build custom queries**: See the [Custom Queries vignette](graphql.html) for template-based query patterns - **Authentication**: See the [Getting Started vignette](meetupr.html) for auth setup - **Advanced auth**: See the [Advanced Authentication vignette](advanced-auth.html) for custom OAuth apps - **API Reference**: Visit for official schema documentation