211 lines
7.6 KiB
Markdown
211 lines
7.6 KiB
Markdown
# Realtime Reference
|
|
|
|
## Contents
|
|
|
|
- [When to Use What](#when-to-use-what)
|
|
- [The @refresh Directive](#the-refresh-directive)
|
|
- [CEL Bindings in Conditions](#cel-bindings-in-conditions)
|
|
- [Implicit Entity Refresh signals](#implicit-entity-refresh-signals)
|
|
|
|
______________________________________________________________________
|
|
|
|
## When to Use What
|
|
|
|
SQL Connect provides three mechanisms for live data updates. Pick the right one
|
|
based on what you're querying:
|
|
|
|
| Scenario | Mechanism | Directive Needed? |
|
|
| ----------------------------------------------------------- | ------------------------ | ----------------------------------- |
|
|
| Single-entity lookup by ID (e.g., `movie(id: $id)`) | **Automatic refresh** | No — SQL Connect handles it |
|
|
| List query that should update when a specific mutation runs | **Event-driven refresh** | `@refresh(onMutationExecuted: ...)` |
|
|
| Any query that should poll at a fixed interval | **Time-based polling** | `@refresh(every: ...)` |
|
|
|
|
List queries require explicit `@refresh` to tell SQL Connect which mutations
|
|
affect the result set.
|
|
|
|
Clients consume all three using `subscribe()` instead of `execute()`. See
|
|
[sdks.md](sdks.md) for per-platform subscribe patterns.
|
|
|
|
______________________________________________________________________
|
|
|
|
## The @refresh Directive
|
|
|
|
`@refresh` is a **repeatable** directive applied to **queries**. It defines when
|
|
connected subscribers should receive updated data.
|
|
|
|
### Time-Based Polling (`every`)
|
|
|
|
Keep the query fresh with a recommended refresh interval. Note that `every` and
|
|
`mutation` signals can be used together; whichever signal arrives first will
|
|
trigger the refresh.
|
|
|
|
```graphql
|
|
query MovieLeaderboard
|
|
@auth(level: PUBLIC)
|
|
@refresh(every: { seconds: 30 }) {
|
|
movies(orderBy: [{ rating: DESC }], limit: 10) {
|
|
id title rating
|
|
}
|
|
}
|
|
```
|
|
|
|
**Constraints:**
|
|
|
|
- The `every` argument takes a duration object: `{ seconds: Int }`
|
|
- **Minimum**: `{ seconds: 10 }` — protects against excessive server load
|
|
- **Maximum**: `{ hours: 1 }` (3600 seconds)
|
|
- Values outside this range fail validation at deploy time
|
|
|
|
Use time-based polling when freshness matters but you don't have a specific
|
|
mutation to listen for (e.g., dashboards aggregating external data, stock
|
|
tickers, activity feeds).
|
|
|
|
### Explicit Mutation Signals (`onMutationExecuted`)
|
|
|
|
Trigger a query refresh when a specific mutation executes. This is the most
|
|
common pattern for keeping lists in sync.
|
|
|
|
```graphql
|
|
# Example with condition (refreshes only when the condition is met)
|
|
query ChatRoom($roomId: UUID!) @auth(level: PUBLIC)
|
|
@refresh(onMutationExecuted: {
|
|
operation: "SendMessage",
|
|
condition: "mutation.variables.roomId == request.variables.roomId"
|
|
}) {
|
|
messages(where: {roomId: {eq: $roomId}}, orderBy: [{createTime: DESC}], limit: 50) {
|
|
author content createTime
|
|
}
|
|
}
|
|
|
|
# Example without condition (refreshes on any execution of the named mutation)
|
|
query ListAllMessages
|
|
@auth(level: PUBLIC)
|
|
@refresh(onMutationExecuted: {
|
|
operation: "SendMessage"
|
|
}) {
|
|
messages { id content }
|
|
}
|
|
```
|
|
|
|
**Arguments:**
|
|
|
|
- **`operation`** (required): The name of the mutation operation to listen for.
|
|
Must match the mutation's operation name exactly.
|
|
- **`condition`** (optional): A CEL expression that must evaluate to `true` for
|
|
the refresh to fire. Without a condition, every execution of the named
|
|
mutation triggers a refresh.
|
|
|
|
It's highly recommended to define fine granular conditions. Inaccurate refresh
|
|
policies could consume Postgres resources and make your app slower.
|
|
|
|
Use conditions to scope refreshes precisely — a review list should only refresh
|
|
when the mutation targets the same movie, not every review across the entire
|
|
app.
|
|
|
|
### Combining Multiple @refresh Directives
|
|
|
|
Since `@refresh` is repeatable, you can combine strategies on a single query:
|
|
|
|
```graphql
|
|
query ActiveOrders($userId: UUID!)
|
|
@auth(level: USER)
|
|
@refresh(onMutationExecuted: {
|
|
operation: "UpdateOrderStatus",
|
|
condition: "request.variables.userId == mutation.variables.userId"
|
|
})
|
|
@refresh(every: { seconds: 60 }) {
|
|
orders(where: { user: { id: { eq: $userId }}, status: { ne: DELIVERED }}) {
|
|
id status total updatedAt
|
|
}
|
|
}
|
|
```
|
|
|
|
This query refreshes whenever an order status changes for this user, *and* polls
|
|
every 60 seconds as a fallback to catch any updates that might not have a direct
|
|
mutation trigger.
|
|
|
|
______________________________________________________________________
|
|
|
|
## CEL Bindings in Conditions
|
|
|
|
The `condition` expression in `onMutationExecuted` has access to two contexts:
|
|
|
|
### `request` — The Query Subscription
|
|
|
|
The state of the query being subscribed to.
|
|
|
|
| Binding | Description |
|
|
| -------------------- | ------------------------------------------------------------ |
|
|
| `request.variables` | Variables passed to the query (e.g., `request.variables.id`) |
|
|
| `request.auth.uid` | UID of the user who subscribed |
|
|
| `request.auth.token` | Full auth token claims of the subscriber |
|
|
|
|
### `mutation` — The Triggering Event
|
|
|
|
The mutation that just executed.
|
|
|
|
| Binding | Description |
|
|
| --------------------- | --------------------------------------------------------------------- |
|
|
| `mutation.variables` | Variables passed to the mutation (e.g., `mutation.variables.movieId`) |
|
|
| `mutation.auth.uid` | UID of the user who executed the mutation |
|
|
| `mutation.auth.token` | Full auth token claims of the mutation executor |
|
|
|
|
### Common Patterns
|
|
|
|
```text
|
|
# Refresh only when the mutation targets the same entity
|
|
"request.variables.id == mutation.variables.id"
|
|
|
|
# Refresh only when the same user who subscribed makes a change
|
|
"request.auth.uid == mutation.auth.uid"
|
|
|
|
# Refresh when a specific field value matches a condition
|
|
"request.auth.uid == mutation.auth.uid && mutation.variables.status == 'PUBLISHED'"
|
|
|
|
# Refresh when a specific flag is set in the mutation
|
|
"mutation.variables.isPublic == true"
|
|
```
|
|
|
|
______________________________________________________________________
|
|
|
|
## Implicit Entity Refresh signals
|
|
|
|
For single-entity lookups by unique identifier, SQL Connect handles refreshes
|
|
automatically — no `@refresh` directive needed.
|
|
|
|
**What qualifies:**
|
|
|
|
- Queries fetching one entity by its primary key: `movie(id: $id)`,
|
|
`user(key: { uid: $uid })`
|
|
- If a single-entity mutation modifies that specific entity, all active
|
|
subscribers automatically receive the update. Supported operations include:
|
|
- `_insert(data)` or `_insertMany(data)`
|
|
- `_upsert(data)` or `_upsertMany(data)`
|
|
- `_update(id)` or `_update(key)`
|
|
- `_delete(id)` or `_delete(key)`
|
|
- **Note**: Bulk operations like `_updateMany` and `_deleteMany` do **not**
|
|
trigger automatic entity refreshes.
|
|
|
|
**What does NOT qualify:**
|
|
|
|
- List queries: `movies(where: {...})`, `users { id name }` — these require
|
|
explicit `@refresh`
|
|
- Nested query with JOINs
|
|
- Aggregation
|
|
- Native SQL
|
|
- Customized Resolver (if supported)
|
|
|
|
```graphql
|
|
# When subscribed to, this query auto-refreshes when movie data changes — no @refresh needed
|
|
query GetMovie($id: UUID!) @auth(level: PUBLIC) {
|
|
movie(id: $id) {
|
|
id title rating description
|
|
reviews_on_movie { rating text user { displayName } }
|
|
}
|
|
}
|
|
```
|
|
|
|
To consume automatic refreshes on the client, use `subscribe()` instead of
|
|
`execute()` — the same client pattern works regardless of whether the refresh is
|
|
automatic or directive-driven.
|