Realtime
Realtime lets a client receive a notification whenever rows in a table are inserted, updated or deleted, without polling. A client opens a WebSocket to the event service with its GC2 (Geo Compute Cloud) JWT, names the relations it wants to follow, and from then on receives batched change events for those tables. Use it to keep a map or dashboard in sync with edits made by other users, to react to imports as they land, or to build collaborative editing on top of your tables.
Events are opt-in per table through the emit_events flag, and access is enforced with the same privileges as the rest of the platform: a sub-user can only subscribe to relations it may read. The same WebSocket also accepts SQL statements. The SDK wraps the connection and subscription handling; the sections below describe the protocol itself.
Overview
The realtime system exposes a lightweight HTTP/WebSocket:
- A WebSocket endpoint for authenticated clients:
wss://event.gc2.io/ - Accepts a JWT token and (for sub‑users) a list of relations they need events from.
- Broadcasts batched event notifications to all connected clients that match relations they listen to.
- A simple health endpoint:
/ping→pong.
Authentication and connection
The WebSocket endpoint is:
- wss://event.gc2.io?token=...&rels=...
Query parameters:
token(required): A GC2 JWT.rels(optional): Comma‑separated list of relations the client wants events from (e.g.,rockhall.inductees,rockhall.stars).
Connection flow:
- Server validates the token in a worker.
- If sub-user, server enforces per‑relation access for each entry in
rels. Unauthorized relations result in an error message and socket close. - On success, the client is registered.
Errors are returned as JSON and the socket is closed. Examples:
- Missing token:
{ "type": "error", "error": "missing_token", "message": "Missing token" } - Invalid token:
{ "type": "error", "error": "invalid_token", "message": "..." } - Unauthorized rels:
{ "type": "error", "error": "not_allowed", "message": "Not allowed to access this resource: <rel>" }
Receiving database change events
To enable database change events, set emit_events to true in the table schema:
PATCH https://api.gc2.io/api/v4/schemas/my_schema/tables/my_table/events
Content-Type: application/json
Authorization: Bearer <token>
{
"emit_events": true
}
Events are batched and delivered to all connected clients subscribed to the table.
Batching defaults:
- 10 notifications or
- 2 seconds (whichever comes first),
- checked every 1 second.
When a batch is flushed, a message is sent to the clients:
{
"type": "batch",
"db": "my_database",
"batch": {
"my_database": {
"my_schema.my_table": {
"INSERT": [["key","value"]],
"UPDATE": [["key","value"]],
"DELETE": [["key","value"]],
"full_data": [ { "col1": "val", "col2": 123 } ]
},
"another_schema.table": {}
}
}
}
Subscriptions
Instead of receiving all events for the database, you can dynamically subscribe to specific tables with server‑side filtering. Send a JSON message over an open WebSocket connection:
{
"type": "subscription",
"id": "sub1",
"schema": "my_schema",
"rel": "my_table"
}
The server acknowledges with:
{
"type": "subscription_ack",
"id": "sub1"
}
Once subscribed, you only receive batches that match your subscription — the server filters before sending.
A single client can register multiple subscriptions on different tables by sending additional subscription messages.
Shapes — filtering and column selection
Subscriptions support shapes: server‑side row filtering and column projection so the client only receives the data it needs.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | Must be "subscription" |
id | string | yes | A client‑chosen identifier for this subscription |
schema | string | yes | Database schema name |
rel | string | yes | Table name |
where | string | no | SQL‑like filter applied to full_data rows before delivery |
columns | string | no | Comma‑separated list of columns to include in full_data (empty = all columns) |
op | string | no | Limit to a single operation: "INSERT", "UPDATE", or "DELETE" (empty = all operations) |
Where syntax
The where clause supports a subset of SQL:
- Comparison operators:
=,!=,<>,>,<,>=,<= - Logical operators:
AND,OR, parentheses for grouping IN/NOT INwith value listsLIKE/ILIKEwith%and_wildcards
Example with shape
{
"type": "subscription",
"id": "active-users",
"schema": "public",
"rel": "users",
"where": "status = 'active' AND age >= 18",
"columns": "id,name,email",
"op": "UPDATE"
}
This subscribes to UPDATE events on public.users, only receiving rows where status = 'active' AND age >= 18, projected to the columns id, name, and email.
GraphQL subscriptions
For GraphQL‑based subscriptions with typed queries and field selection, see GraphQL Subscriptions.
Client examples
Broadcast (legacy)
JavaScript (browser):
<script>
const token = "<your-jwt>";
const rels = "public.parcels,public.addresses";
const ws = new WebSocket(`wss://event.gc2.io/?token=${encodeURIComponent(token)}&rels=${encodeURIComponent(rels)}`);
ws.onopen = () => {
console.log("connected");
ws.send("SELECT NOW() AS ts");
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'batch') {
console.log("DB batch:", msg.batch);
} else {
console.log("SQL result:", msg);
}
};
ws.onclose = () => console.log("closed");
ws.onerror = (err) => console.error("ws error", err);
</script>
Subscription with shape
const token = "<your-jwt>";
const ws = new WebSocket(`wss://event.gc2.io/?token=${encodeURIComponent(token)}`);
ws.onopen = () => {
// Subscribe to updates on public.users with filtering
ws.send(JSON.stringify({
type: "subscription",
id: "active-users",
schema: "public",
rel: "users",
where: "status = 'active'",
columns: "id,name,email",
op: "UPDATE"
}));
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'subscription_ack') {
console.log(`Subscribed: ${msg.id}`);
} else if (msg.type === 'batch') {
console.log("Filtered batch:", msg.batch);
}
};
Node (ws)
import WebSocket from 'ws';
const token = process.env.GC2_TOKEN;
const ws = new WebSocket(`wss://event.gc2.io/?token=${encodeURIComponent(token)}`);
ws.on('open', () => ws.send('SELECT 42 AS answer'));
ws.on('message', (data) => console.log(JSON.parse(data.toString())));