---
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 <https://www.meetup.com/api/playground>  
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 <https://www.meetup.com/graphql/> for official schema documentation
