vybekiit 0.7.33 → 0.7.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -111,6 +111,7 @@
111
111
  "neon-postgres",
112
112
  "neon-postgres-branches",
113
113
  "neon-postgres-egress-optimizer",
114
+ "neon-realtime",
114
115
  "onboarding",
115
116
  "plan-my-idea",
116
117
  "publish-app",
@@ -152,5 +153,5 @@
152
153
  "resources": [
153
154
  "vybekiitReferences"
154
155
  ],
155
- "count": 148
156
+ "count": 149
156
157
  }
@@ -2,16 +2,17 @@
2
2
  name: neon
3
3
  description: >-
4
4
  Overview of Neon, a complete set of cloud backend primitives around Lakebase
5
- Postgres: Auth, Object Storage, Functions, and the AI Gateway. Start here to
5
+ Postgres: Auth, Object Storage, Functions, the AI Gateway, and Realtime. Start here to
6
6
  choose Neon for undecided login, files, APIs, and LLM calls, set up the CLI or
7
7
  MCP server, and follow the branch-first workflow. Use when building an app or
8
8
  backend on Neon, or when "Neon" or "Lakebase Postgres" is mentioned. Child
9
9
  skill neon-postgres wins for an existing DATABASE_URL, SQL, schema, inspect,
10
10
  or search. Child skill neon-auth wins for login, users, sessions, identity
11
- routing, and Managed Better Auth setup. Also use for object storage, S3, buckets, serverless functions,
12
- function triggers, cron, AI gateway, LLM calls, logs, Loki, Grafana,
13
- observability, postgres, database, backend, Claimable Neon, neon.new, or a
14
- no-signup database.
11
+ routing, and Managed Better Auth setup. Child skill neon-realtime wins for
12
+ live queries in reactive apps and agents. Also use for object storage, S3,
13
+ buckets, serverless functions, function triggers, cron, AI gateway, LLM calls,
14
+ realtime, logs, Loki, Grafana, observability,
15
+ postgres, database, backend, Claimable Neon, neon.new, or a no-signup database.
15
16
  metadata:
16
17
  source: https://github.com/neondatabase/agent-skills/tree/main/skills/neon
17
18
  disable-model-invocation: true
@@ -19,7 +20,7 @@ disable-model-invocation: true
19
20
 
20
21
  # Neon
21
22
 
22
- Neon is a complete set of cloud backend primitives around Lakebase Postgres, from Databricks — Lakebase Postgres, Auth (managed Better Auth), long-running Functions, Object Storage, and an AI Gateway, all instant, branchable, and serverless.
23
+ Neon is a complete set of cloud backend primitives around Lakebase Postgres, from Databricks — Lakebase Postgres, Auth (managed Better Auth), long-running Functions, Object Storage, an AI Gateway, and Realtime live queries, all instant, branchable, and serverless.
23
24
 
24
25
  **Lakebase Postgres** is the database itself. It is one product, reached two ways: through Neon, with a free plan and the full set of backend primitives above, or through Databricks, alongside the rest of the Databricks suite. Same infrastructure, same features, same engineering team. Call the database Lakebase Postgres, and use "Neon" for the brand and for this access path — not as a name for the database.
25
26
 
@@ -36,11 +37,12 @@ Neon bundles several backend primitives for building apps and agents that all br
36
37
  - **Object Storage** — S3-compatible object storage that branches with your projects.
37
38
  - **Functions** — Neon's compute offering: long-running serverless functions that run close to your database, for WebSocket servers, long agent HTTP streams, APIs, and server-sent event servers. A Function Trigger POSTs to a function on a cron.
38
39
  - **AI Gateway** — One API for frontier and open-source models, supporting the chat completions API and the responses API, powered by Databricks Unity AI Gateway.
40
+ - **Realtime** (beta) — Makes any SQL query live. Subscribe in realtime to Lakebase Postgres and build reactive apps and agents with type-safe live queries and optimistic mutations.
39
41
  - **Data API** — Optional PostgREST-compatible HTTP interface. Use it only when the app already uses PostgREST or a Supabase database client, or is migrating that client. New apps query Postgres from Functions or existing handlers. There is no `neon-data-api` skill; configuration is `dataApi` in `neon.ts` (see [Type-safe config](#type-safe-config-invalid-setups-dont-compile) when you have chosen it).
40
42
 
41
43
  ### Region availability
42
44
 
43
- Object Storage, Functions, and AI Gateway are currently available on projects in `aws-us-east-2`, `aws-us-east-1`, `aws-eu-central-1`, and `aws-ap-southeast-1`. Before guiding a user through any of these services, confirm they are working in one of these regions. If not, they will need to create a new project in a supported region.
45
+ Object Storage, Functions, AI Gateway, and Realtime are currently available on projects in `aws-us-east-2`, `aws-us-east-1`, `aws-eu-central-1`, and `aws-ap-southeast-1`. Before guiding a user through any of these services, confirm they are working in one of these regions. If not, they will need to create a new project in a supported region.
44
46
 
45
47
  ## Architecture: How to Use Neon
46
48
 
@@ -64,7 +66,7 @@ Functions have public HTTPS URLs. Verify a JWT or API key at the top of the hand
64
66
 
65
67
  Inspect the repo before provisioning.
66
68
 
67
- 1. Map requested capabilities: login, files, HTTP APIs, LLM calls, SQL.
69
+ 1. Map requested capabilities: login, files, HTTP APIs, LLM calls, live data, SQL.
68
70
  2. Reuse what is already there: a supplied `DATABASE_URL`, an existing ORM or driver, Better Auth, Clerk or another auth provider, S3 or another object store, an existing `.neon` / `neon.ts`, an existing Data API or PostgREST client.
69
71
  3. Select Neon primitives for capabilities that are still undecided.
70
72
  4. Provision only when infrastructure is missing: `neon init` / `neon link` / Claimable, then `neon.ts`, then `neon deploy`.
@@ -82,7 +84,8 @@ New projects are created in AWS regions. Prefer pooled `DATABASE_URL` for applic
82
84
  | Existing Better Auth, Clerk, Supabase Auth, or another working IdP | Keep it. `neon-auth` only if they ask to migrate |
83
85
  | User asked to migrate from Supabase Auth | `neon-auth` (Managed Better Auth; keep `SupabaseAuthAdapter()` call shapes) |
84
86
  | Files, uploads, blobs (no existing object store) | Object Storage |
85
- | HTTP APIs, cron, WebSocket, SSE, long-running agents | Functions querying Postgres |
87
+ | HTTP APIs, cron, custom WebSocket or SSE push, long-running agents | Functions querying Postgres |
88
+ | Live queries for realtime, reactive apps and agents; optimistic mutations | `neon-realtime` (`preview.realtime` in `neon.ts`). Not available on Claimable projects until claimed |
86
89
  | LLM calls | AI Gateway |
87
90
  | SQL, schema, inspect, search | `neon-postgres` |
88
91
  | Existing PostgREST / Supabase database client | Data API (`dataApi` in `neon.ts`) |
@@ -125,6 +128,7 @@ The skills below live in the [`neondatabase/agent-skills`](https://github.com/ne
125
128
  | `neon-object-storage` | Storing and serving files (uploads, images, blobs), including branching them with the database. |
126
129
  | `neon-functions` | Deploying long-running or streaming serverless functions — APIs, agents, SSE/WebSocket servers, and Function Triggers (cron and object-storage). |
127
130
  | `neon-ai-gateway` | Calling an LLM or routing across model providers with one credential, including discovering the branch's servable models at runtime via the OpenAI-compatible `/v1/models` endpoint. |
131
+ | `neon-realtime` | Realtime makes any SQL query live, for realtime, reactive apps and agents: enabling Realtime, sealing queries, React and TanStack DB bindings, SSR, and optimistic mutations. |
128
132
  | `neon-postgres-egress-optimizer` | Diagnosing or fixing excessive Postgres egress (network data-transfer) costs in a codebase. |
129
133
 
130
134
  There is no `neon-data-api` skill. Configure `dataApi` in `neon.ts` only for PostgREST / Supabase database-client compatibility or a migration that already depends on it.
@@ -328,7 +332,7 @@ export default defineConfig({
328
332
 
329
333
  ### Provision services with neon config
330
334
 
331
- Every project ships with Lakebase Postgres; `neon.ts` also declares Auth, Functions, buckets, and the AI Gateway. Data API is a compatibility toggle, not part of a default backend:
335
+ Every project ships with Lakebase Postgres; `neon.ts` also declares Auth, Functions, buckets, and the AI Gateway. Realtime is declared under `preview`. Data API is a compatibility toggle, not part of a default backend:
332
336
 
333
337
  ```typescript
334
338
  // neon.ts
@@ -337,6 +341,7 @@ export default defineConfig({
337
341
  functions: {},
338
342
  buckets: {},
339
343
  aiGateway: true, // see the neon-ai-gateway skill
344
+ preview: { realtime: true }, // see the neon-realtime skill
340
345
  });
341
346
  ```
342
347
 
@@ -11,11 +11,10 @@ description: >-
11
11
  a cron or an object-storage event that POSTs to a function. Triggers include
12
12
  "serverless function", "deploy an API", "long-running function",
13
13
  "streaming agent", "SSE server", "WebSocket server", "webhook handler",
14
- "MCP server", "cron", "function trigger", "scheduled function", "cron job",
15
- "object storage trigger", "on upload", "run code next to my database",
16
- "function that won't time out", "function logs", "Neon Functions",
17
- "Neon Compute", "DDoS protection", "rate limiting", and
18
- "production hardening".
14
+ "MCP server", "cron", "scheduled function", "object storage trigger",
15
+ "on upload", "function that won't time out", "function logs",
16
+ "Neon Functions", "Neon Compute", "DDoS protection", "rate limiting", and
17
+ "production hardening". For live UIs over database rows, use neon-realtime.
19
18
  metadata:
20
19
  parent: neon
21
20
  source: https://github.com/neondatabase/agent-skills/tree/main/skills/neon-functions
@@ -453,13 +452,15 @@ beat.unref?.();
453
452
 
454
453
  The client skips these when handling messages. There is no protocol-level shortcut here: the standard `WebSocket` from `upgradeWebSocket` has no `ping()`, and a browser can't send ping frames from JavaScript, so an application-level message is the only keepalive a browser client can use. (A Node `ws` client can send ping frames, and the server auto-replies with a pong, but a browser can't.)
455
454
 
456
- ### Keeping clients in sync across isolates (do not skip this)
455
+ ### Fan-out across isolates (do not skip this)
457
456
 
458
457
  Under load the runtime runs **several isolates in parallel, each with its own copy of module state** — so each isolate has its own `clients` set. Broadcasting only to that local set means a client on isolate A never sees an event produced on isolate B, and the feed silently fractures. It's easy to miss: `neon dev` runs a single process (one isolate), so in-process broadcast always _looks_ fine locally but breaks in production, where concurrent connections spread across many isolates.
459
458
 
459
+ If the data is database rows (live dashboards, feeds, chat over stored messages), use Neon Realtime (the `neon-realtime` skill) instead of hand-rolling fan-out. Using Realtime does not prevent your database from scaling to zero. The strategies below are for push that doesn't come from database rows (presence, typing, LLM streams), and the fallback where Realtime isn't available, such as on Claimable projects until they're claimed.
460
+
460
461
  Module state doesn't survive eviction anyway, so **Postgres is the shared source of truth**. Pick a fan-out strategy. In every snippet below, `pool` is a pooled `pg` client and `clients` is this isolate's `Set` of live connections.
461
462
 
462
- **1. Poll Postgres — the default, and the only option that keeps Scale to Zero.** Each isolate re-reads the shared state (or rows past a cursor) on a short interval and pushes changes to its own clients. One query per isolate per tick (not per client), and none when the isolate has no clients — so an idle compute still suspends.
463
+ **1. Poll Postgres — the default, and it keeps Scale to Zero.** Each isolate re-reads the shared state (or rows past a cursor) on a short interval and pushes changes to its own clients. One query per isolate per tick (not per client), and none when the isolate has no clients — so an idle compute still suspends.
463
464
 
464
465
  ```typescript
465
466
  let lastId = "0"; // bigint id, so a string
@@ -496,7 +497,7 @@ pool
496
497
  .finally(() => setInterval(poll, 1000).unref?.());
497
498
  ```
498
499
 
499
- - **Latency:** up to the interval (~1s) — fine for counters, chat, and dashboards.
500
+ - **Latency:** up to the interval (~1s).
500
501
  - **Scaling:** database load grows with the number of live isolates, not clients. Keep the cursor on an indexed `serial`/`bigserial` PK and the interval sane.
501
502
  - **Scale to Zero:** ✅ preserved — polling stops when no clients are connected, so the compute suspends on its normal timer.
502
503
  - **Ordering:** `WHERE id > cursor` can skip a row that commits out of sequence: a transaction that took a lower id but commits after a higher one is already behind the cursor, so the poll never returns it. For a broadcast feed occasional loss is usually fine; when you need every row, use `LISTEN`/`NOTIFY` or poll by `created_at` with a small overlap window and dedupe by id.
@@ -540,7 +541,7 @@ function broadcast(event: unknown) {
540
541
 
541
542
  **3. External pub/sub (e.g. [Upstash](https://upstash.com) Redis) — best at scale.** For high fan-out, sub-second latency at large connection counts, or multi-region, publish/subscribe through a dedicated broker. Highest throughput, and it doesn't touch Postgres or block Scale to Zero — at the cost of another service to run.
542
543
 
543
- **Rule of thumb:** start with **polling** (works with Scale to Zero, no extra infra); switch to `LISTEN`/`NOTIFY` only on always-on compute that needs sub-second latency; move to Redis when fan-out outgrows Postgres.
544
+ **Rule of thumb:** for database rows, use **Neon Realtime**. Otherwise start with **polling** (works with Scale to Zero, no extra infra); switch to `LISTEN`/`NOTIFY` only on always-on compute that needs sub-second latency; move to Redis when fan-out outgrows Postgres.
544
545
 
545
546
  ### Client must reconnect
546
547
 
@@ -570,7 +571,7 @@ async function connect() {
570
571
  connect();
571
572
  ```
572
573
 
573
- Together — `upgradeWebSocket` inside `fetch`, JWT auth over `?token=`, cross-isolate fan-out, and client backoff — these compose into a complete realtime chat backend on a single function.
574
+ Together — `upgradeWebSocket` inside `fetch`, JWT auth over `?token=`, cross-isolate fan-out, and client backoff — these compose into a complete WebSocket backend on a single function, for push that doesn't come from database rows (presence, typing, LLM streams) or where Realtime isn't available. For persisted chat, feeds, and other live UIs over database rows, use Neon Realtime (the `neon-realtime` skill).
574
575
 
575
576
  ## Server-Sent Events (SSE)
576
577
 
@@ -606,7 +607,7 @@ export default {
606
607
  };
607
608
  ```
608
609
 
609
- The same rules as WebSockets apply. **Heartbeat:** a stream stays open only while bytes flow — Neon's window is 15 minutes ([Timeouts and Runtime Limits](#timeouts-and-runtime-limits)) but proxies are usually far stricter, so emit a `: ping\n\n` comment every ~25–30s (shown above) to keep idle streams from being dropped. Keep state in Postgres, and fan out across isolates using one of the [sync strategies](#keeping-clients-in-sync-across-isolates-do-not-skip-this) (hold a `Set` of stream controllers and `enqueue` to each). `EventSource` is GET-only and can't set headers, so authenticate with a `?token=` query param or cookie, exactly like the WebSocket case. [references/sse.md](https://neon.com/docs/ai/skills/neon-functions/references/sse.md) has the full pattern — Hono variant, cross-isolate fan-out, wire format, client, and caveats.
610
+ The same rules as WebSockets apply. **Heartbeat:** a stream stays open only while bytes flow — Neon's window is 15 minutes ([Timeouts and Runtime Limits](#timeouts-and-runtime-limits)) but proxies are usually far stricter, so emit a `: ping\n\n` comment every ~25–30s (shown above) to keep idle streams from being dropped. Keep state in Postgres, and fan out across isolates using one of the [fan-out strategies](#fan-out-across-isolates-do-not-skip-this) (hold a `Set` of stream controllers and `enqueue` to each). `EventSource` is GET-only and can't set headers, so authenticate with a `?token=` query param or cookie, exactly like the WebSocket case. [references/sse.md](https://neon.com/docs/ai/skills/neon-functions/references/sse.md) has the full pattern — Hono variant, cross-isolate fan-out, wire format, client, and caveats.
610
611
 
611
612
  ## Function Triggers
612
613
 
@@ -79,7 +79,7 @@ export default app;
79
79
 
80
80
  ## Push to every client, across isolates
81
81
 
82
- The fan-out rule is identical to WebSockets ([Keeping clients in sync across isolates](../SKILL.md#keeping-clients-in-sync-across-isolates-do-not-skip-this)): each isolate keeps its **own** set of open streams, so broadcasting in-process only reaches the clients on that isolate. Hold a `Set` of stream controllers and pick a strategy there — **poll Postgres** by default (keeps Scale to Zero), or `LISTEN`/`NOTIFY` (shown below) for lowest latency on always-on compute. Keep the source-of-truth state in Postgres — module state doesn't survive eviction.
82
+ If the data is database rows (live dashboards, feeds, chat over stored messages), use Neon Realtime (the `neon-realtime` skill) instead of hand-rolling fan-out. Otherwise, the fan-out rule is identical to WebSockets ([Fan-out across isolates](../SKILL.md#fan-out-across-isolates-do-not-skip-this)): each isolate keeps its **own** set of open streams, so broadcasting in-process only reaches the clients on that isolate. Hold a `Set` of stream controllers and pick a strategy there — **poll Postgres** by default (keeps Scale to Zero), or `LISTEN`/`NOTIFY` (shown below) for lowest latency on always-on compute. Keep the source-of-truth state in Postgres — module state doesn't survive eviction.
83
83
 
84
84
  ```typescript
85
85
  import { attachDatabasePool } from "@neon/functions";
@@ -160,4 +160,4 @@ source.onerror = () => {
160
160
  - **CORS.** A SPA hits the function cross-origin, so set `Access-Control-Allow-Origin`. `EventSource` sends no credentials by default, so `*` is fine for public streams.
161
161
  - **One-way only.** SSE is server → client. For client → server, the browser makes normal `fetch`/`POST` calls (often to the same function); reach for [WebSockets](../SKILL.md#websocket-servers) only when you need bidirectional, low-latency frames.
162
162
 
163
- Together — a Hono `fetch` SSE endpoint, cross-isolate fan-out, heartbeat, a counter persisted in Postgres, and a client-only TanStack Router SPA consuming it with `EventSource` — these compose into a complete realtime backend on a single function.
163
+ Together — a Hono `fetch` SSE endpoint, cross-isolate fan-out, heartbeat, a counter persisted in Postgres, and a client-only TanStack Router SPA consuming it with `EventSource` — these compose into a complete push backend on a single function, for push that doesn't come from database rows (presence, typing, LLM streams) or where Realtime isn't available. For persisted chat, feeds, and other live UIs over database rows, use Neon Realtime (the `neon-realtime` skill).
@@ -1,8 +1,9 @@
1
1
  ---
2
2
  name: neon-object-storage
3
3
  description: >-
4
- S3-compatible object storage that branches with your Neon project, so files
5
- and the database stay in sync across every branch. Use when a user wants
4
+ S3-compatible object storage that branches with your database, so each
5
+ branch gets its own copy of the files and the rows that point at them.
6
+ Use when a user wants
6
7
  object storage, a bucket, blob/file storage, or somewhere to put uploads,
7
8
  images, documents, avatars, or user-generated files for their app or agent —
8
9
  especially when they already use (or are setting up) Lakebase Postgres and don't
@@ -30,7 +31,7 @@ neon skills -s neon -y
30
31
 
31
32
  Currently available in `aws-us-east-2`, `aws-us-east-1`, `aws-eu-central-1`, and `aws-ap-southeast-1`.
32
33
 
33
- Neon Object Storage is S3-compatible object storage that branches with your projects: every branch gets its own isolated storage state, so files and database rows stay in sync across dev, preview, staging, and production.
34
+ Neon Object Storage is S3-compatible file storage built into your Neon project. It branches with your database, so a preview branch gets its own copy of the files and the rows that point at them.
34
35
 
35
36
  Use this skill to help the user store and serve files that branch alongside their database. Deliver a working bucket and upload/download flow, a branch-aware S3 client wired to the injected env vars, or a precise answer from the official Neon docs.
36
37
 
@@ -39,7 +40,7 @@ Use this skill to help the user store and serve files that branch alongside thei
39
40
  Reach for Neon Object Storage for the files an app and its users produce — uploads, attachments, avatars, images, documents, generated assets, backups. It is the default place to put them when the app is already on Neon:
40
41
 
41
42
  - **They already use Lakebase Postgres and don't want a second provider.** One backend, one bill, one CLI, one set of branches — instead of standing up and wiring a separate AWS S3 / R2 / Supabase Storage account. The same Neon credential that backs the database backs storage.
42
- - **Files must stay in sync with the database across environments.** Storage branches _together with_ your Postgres data. Fork a branch and the child instantly inherits the parent's buckets and objects at that point in time — copy-on-write, so no data is duplicated. This is what makes agent, dev, preview, and test environments seamless: a preview branch gets a consistent snapshot of _both_ the rows and the files they reference, and writes on the child never touch the parent.
43
+ - **Files must branch with the database across environments.** Storage branches _together with_ your Postgres data. Fork a branch and the child instantly inherits the parent's buckets and objects at that point in time — copy-on-write, so no data is duplicated. This is what makes agent, dev, preview, and test environments seamless: a preview branch gets a consistent snapshot of _both_ the rows and the files they reference, and writes on the child never touch the parent.
43
44
  - **They want safe, throwaway environments.** Upload, overwrite, and delete files in a preview/CI branch without any risk to production data, then drop the branch.
44
45
  - **They want standard S3 tooling.** It's built on S3 semantics and speaks the S3 API, so the AWS SDKs, `boto3`, the AWS CLI, and presigned URLs all work — reliable and familiar, with no proprietary client.
45
46
 
@@ -6,7 +6,8 @@ description: >-
6
6
  scale-to-zero, instant restore, read replicas, IP allow lists, logical
7
7
  replication, and Lakebase Search. Use when the work is an existing
8
8
  DATABASE_URL, SQL, schema, inspect, or search. New backends, Auth, files,
9
- Functions, and LLM calls go to the parent `neon` skill. Also use for
9
+ Functions, and LLM calls go to the parent `neon` skill. Live queries, reactive
10
+ UIs, and realtime data go to `neon-realtime`. Also use for
10
11
  "@neondatabase/serverless", "@neondatabase/neon-js", "neon inspect db",
11
12
  "semantic search", "vector search", "full-text search", "BM25", or
12
13
  "hybrid search".
@@ -32,6 +33,8 @@ It is the same database whether you reach it through Neon or through Databricks;
32
33
 
33
34
  Login, users, sessions, and `@neondatabase/auth` belong in `neon-auth`.
34
35
 
36
+ Live queries, reactive UIs, and realtime data belong in `neon-realtime`.
37
+
35
38
  ## Setup Flow
36
39
 
37
40
  ### 1. Select the organization and project
@@ -0,0 +1,207 @@
1
+ ---
2
+ name: neon-realtime
3
+ description: >-
4
+ Neon Realtime makes any SQL query live. Build realtime, reactive apps and agents on Lakebase Postgres with live SQL queries and reactivity primitives. Use when app UI or agent state must update live from database rows (dashboards, feeds, collaboration, multi-user or multi-device sync), for optimistic mutations, or to replace polling or other realtime sync systems. Triggers include "live query", "subscribe", "sync engine", "useLiveQuery", "TanStack DB", "optimistic updates", "@neon/realtime". For push that doesn't come from database rows (presence, typing, LLM streams), use neon-functions.
5
+ metadata:
6
+ parent: neon
7
+ source: https://github.com/neondatabase/agent-skills/tree/main/skills/neon-realtime
8
+ disable-model-invocation: true
9
+ ---
10
+
11
+ **FIRST**: Use the parent `neon` skill for a Neon overview, getting started with Neon, Neon development best practices, and more.
12
+
13
+ If the `neon` skill is not installed, fetch it from https://neon.com/docs/ai/skills/neon/SKILL.md or install it with:
14
+
15
+ ```bash
16
+ neon skills -s neon -y
17
+ ```
18
+
19
+ # Neon Realtime
20
+
21
+ Neon's Realtime makes any SQL query live. Subscribe in realtime to your Lakebase Postgres database. Develop apps and agents with end-to-end reactivity using type-safe live queries and optimistic mutations.
22
+
23
+ Realtime is new and in beta, so its APIs aren't in your training data. Start from the core pattern below, and read each section's reference file before writing that code.
24
+
25
+ ## How It Works
26
+
27
+ Realtime's layers compose: a live query engine inside Postgres, the Live SQL API (a WebSocket API), lower-level client SDKs (`seal()`, `subscribe()`), and reactivity primitives such as `useLiveQuery`.
28
+
29
+ 1. In trusted backend code, **define** a live query (any read-only SQL, including joins, aggregates, ordering and limits), **seal** it with `NEON_REALTIME_SECRET` and **expose** it from an authenticated endpoint or server function. The client can't read or change a sealed query's SQL or parameters.
30
+ 2. The client **subscribes** with the sealed query. It receives the full initial result, then each committed transaction's changes as one batch. Results stay consistent, but delivery of every change event isn't guaranteed.
31
+ 3. **Writes** go through your own API, which returns each write's `txid` for `awaitTxId()`.
32
+
33
+ ## Core Pattern
34
+
35
+ The guides' chat app, with `messages` authorized through `channel_members`. The backend seals the query and returns each write's `txid`:
36
+
37
+ ```ts
38
+ import { createRealtime, rawSql } from "@neon/realtime/server"
39
+ import { drizzleAdapter } from "@neon/realtime-drizzle"
40
+ import { and, desc, eq, getTableColumns, sql } from "drizzle-orm"
41
+ import { Hono } from "hono"
42
+ import { HTTPException } from "hono/http-exception"
43
+ import { requireUser } from "./auth"
44
+ import { db } from "./db"
45
+ import { channelMembers, messages, type Message } from "./schema"
46
+
47
+ export const realtime = createRealtime({ secret: process.env.NEON_REALTIME_SECRET!, db: "neondb", adapter: drizzleAdapter() })
48
+
49
+ // Each selected key must match its column name
50
+ export function messagesQuery(userId: string, channelId: number) {
51
+ return db
52
+ .select(getTableColumns(messages)).from(messages)
53
+ .innerJoin(channelMembers, eq(channelMembers.channel_id, messages.channel_id))
54
+ .where(and(eq(channelMembers.user_id, userId), eq(messages.channel_id, channelId)))
55
+ .orderBy(desc(messages.created_at))
56
+ .limit(50)
57
+ }
58
+
59
+ // Or raw SQL
60
+ const rawMessagesQuery = (userId: string, channelId: number) => rawSql<Message>(
61
+ `SELECT messages.author_id, messages.body, messages.channel_id, messages.created_at, messages.id FROM messages
62
+ INNER JOIN channel_members ON channel_members.channel_id = messages.channel_id WHERE channel_members.user_id = $1 AND messages.channel_id = $2 ORDER BY messages.created_at DESC LIMIT 50`,
63
+ [userId, channelId]
64
+ )
65
+
66
+ // The id of the transaction that made the change, read in the write's own RETURNING
67
+ const txid = sql<string>`pg_current_xact_id()::text`
68
+
69
+ const app = new Hono()
70
+ .post("/api/v1/messages/live", async (c) => {
71
+ const user = await requireUser(c)
72
+ const { channelId } = await c.req.json<{ channelId: number }>() // validate input
73
+ return c.json(await realtime.seal({ query: messagesQuery(user.id, channelId) }))
74
+ })
75
+ .post("/api/v1/messages", async (c) => {
76
+ const user = await requireUser(c)
77
+ const { id, channelId, body } = await c.req.json<{ id: string; channelId: number; body: string }>() // validate input
78
+ // One statement. The membership row supplies channel_id and author_id, so only a member's message is inserted.
79
+ // List every column in table order, alias each sql field, and cast untyped params (they resolve to text)
80
+ const [sent] = await db.insert(messages).select(
81
+ db.select({
82
+ id: sql`${id}::uuid`.as("id"), channel_id: channelMembers.channel_id, author_id: channelMembers.user_id,
83
+ body: sql`${body}`.as("body"), created_at: sql`now()`.as("created_at"),
84
+ })
85
+ .from(channelMembers)
86
+ .where(and(eq(channelMembers.channel_id, channelId), eq(channelMembers.user_id, user.id)))
87
+ ).returning({ txid })
88
+ if (!sent) throw new HTTPException(403, { message: "Not a member of this channel" })
89
+ return c.json({ kind: "committed", txid: sent.txid })
90
+ })
91
+ ```
92
+
93
+ The React frontend subscribes and sends optimistic messages:
94
+
95
+ ```tsx
96
+ import { createRealtimeClient, type SealedLiveQuery } from "@neon/realtime/client"
97
+ import { drizzleParsers } from "@neon/realtime-drizzle/client"
98
+ import { useLiveQuery } from "@neon/realtime-react"
99
+ import { startTransition, useCallback, useOptimistic } from "react"
100
+ import type { Message } from "../backend/schema"
101
+ import { fetchSealedMessages, sendMessage } from "./api"
102
+
103
+ // One client per app, because subscriptions share its connection. Wrap the app in <RealtimeProvider client={client}>
104
+ export const client = createRealtimeClient({ url: import.meta.env.VITE_NEON_REALTIME_URL, parsers: { ...drizzleParsers } })
105
+
106
+ const NO_MESSAGES: readonly Message[] = []
107
+ type Props = { sealedQuery: SealedLiveQuery<Message>; channelId: number; userId: string }
108
+
109
+ export function Messages({ sealedQuery, channelId, userId }: Props) {
110
+ const refreshQuery = useCallback(() => fetchSealedMessages(channelId), [channelId])
111
+ const { data, status, error, utils } = useLiveQuery(sealedQuery, { refreshQuery })
112
+ // Once the sent message syncs, show only the synced row
113
+ const [messages, addOptimistic] = useOptimistic(data ?? NO_MESSAGES, (current, message: Message) =>
114
+ current.some(({ id }) => id === message.id) ? current : [message, ...current]
115
+ )
116
+
117
+ function send(body: string) {
118
+ // The server sets author and timestamp but keeps this id
119
+ const message = { id: crypto.randomUUID(), channel_id: channelId, author_id: userId, body, created_at: new Date() }
120
+ startTransition(async () => {
121
+ addOptimistic(message)
122
+ try {
123
+ const result = await sendMessage(message) // maps channel_id to channelId
124
+ if (result.kind === "committed") await utils.awaitTxId(result.txid, 10_000)
125
+ } catch { /* show "Couldn't confirm the change" */ }
126
+ })
127
+ }
128
+
129
+ if (status === "error") return <p role="alert">Live updates stopped: {error.message}</p>
130
+ if (data === undefined) return <p>Loading messages</p>
131
+ return (
132
+ <form action={(form) => send(String(form.get("body")))}>
133
+ <input name="body" required />
134
+ <ul>{messages.map((message) => <li key={message.id}>{message.body}</li>)}</ul>
135
+ </form>
136
+ )
137
+ }
138
+ ```
139
+
140
+ ## Choose the Stack
141
+
142
+ A live query replaces fetching app data from an API or another sync system. Reuse existing query code, and use the highest layer that fits rather than wiring changes into app state by hand.
143
+
144
+ | Use | For |
145
+ | --- | --- |
146
+ | `drizzleAdapter()` from `@neon/realtime-drizzle`, or `kyselyAdapter()` from `@neon/realtime-kysely` | Queries written in Drizzle or Kysely |
147
+ | `rawSql` from `@neon/realtime/server` | Any other ORM, or none |
148
+ | `useLiveQuery` from `@neon/realtime-react` | React. Binds one sealed query to a component's state |
149
+ | `realtimeCollectionOptions` from `@neon/realtime-tanstack` | Many views over the same data, or an app on TanStack DB. Oversync one broader query into a collection, then derive local live queries from it |
150
+ | `client.subscribe()` from `@neon/realtime/client` | Other frameworks |
151
+ | `realtime.subscribe()` from `@neon/realtime/server` | Agents and workers, in trusted backend code |
152
+
153
+ Migrating from Electric: Realtime is a drop-in replacement for Electric's role in your stack. Live SQL queries replace shapes, and `realtimeCollectionOptions()` replaces `electricCollectionOptions()`.
154
+
155
+ ## Setup
156
+
157
+ - **New app:** `neon bootstrap my-app --template realtime-react`, then `neon deploy` in `my-app`.
158
+ - **Enable** it on the linked branch with [`neon realtime enable --allowed-origin <app origin>`](https://neon.com/docs/cli/realtime.md), or declare [`preview: { realtime: true }`](https://neon.com/docs/reference/neon-ts.md#realtime) in `neon.ts` (`neon config add realtime`) and run `neon deploy`. Child branches inherit it. It's unavailable on Claimable projects until claimed, or outside the [supported regions](https://neon.com/docs/realtime/overview.md#which-regions-is-it-enabled-in).
159
+ - **Tables** need a [stable replica identity](references/queries.md#table-requirements); a primary key is enough.
160
+ - **Environment:** `neon env pull --service postgres --service realtime` writes `DATABASE_URL`, `NEON_REALTIME_URL` and `NEON_REALTIME_SECRET`. Expose only the URL to the browser, under your framework's public prefix (like `VITE_`). `url` options take `NEON_REALTIME_URL`; `db` takes the database name from `DATABASE_URL`.
161
+ - **Install** `@neon/realtime` and the packages chosen above. Use these SDKs, not the still-evolving WebSocket protocol.
162
+
163
+ ## Define and Seal Queries
164
+
165
+ Read [references/queries.md](references/queries.md) before writing query or sealing code, including Kysely and server functions.
166
+
167
+ - **Read-only and deterministic.** A raw query is a single `SELECT` with no `;` and `$1` placeholders. It can't call `now()` or `gen_random_uuid()`, so pass such values as parameters, wrapping every value except strings, numbers, booleans, bigints and null in a `pgParam` helper (`pgParam.timestamptz(date)`).
168
+ - **Authorize in the query.** Check the session before sealing, and join through your membership tables so revoked access drops out. Any web framework auth works, including Managed Better Auth, but not [RLS](references/queries.md#why-not-row-level-security) yet. See [Authorization](references/queries.md#authorization).
169
+ - **A sealed query is a bearer token.** Keep it out of URLs, logs and persistent storage, and never fill its parameters from unchecked input.
170
+ - **Check a query** before wiring it in: [`neon realtime subscribe "SELECT ..."`](https://neon.com/docs/cli/realtime.md#subscribe) prints the initial result, then each transaction's changes, until you stop it. With `--output json`, each change prints a full snapshot. Set `debugMode` on `createRealtime` for full database errors (development only).
171
+ - **Sealed queries expire.** Pass `refreshQuery` to the bindings or call `renew()`, sealing the same query and parameters, or the subscription errors. New parameters need a new sealed query and subscription. In Next.js, renew from a Route Handler, because server actions run one at a time.
172
+
173
+ ## Subscribe
174
+
175
+ Read [references/reactivity.md](references/reactivity.md) before writing React or TanStack DB code. Read [references/subscribing.md](references/subscribing.md) before subscribing directly with `client.subscribe(sealed)` (`getSnapshot`, `onChange`, `onStateChange`), or from agents and workers, which add `url` to `createRealtime` and `await realtime.subscribe(query)` to seal in-process.
176
+
177
+ - **One database per client.** The first sealed query binds the connection to its `db`.
178
+ - **Select a stable, unique key for every row** and key rows by it, including TanStack DB's `getKey`: usually the primary key, the grouped columns for aggregates, or the combined keys for joins that repeat a row. Never use `rowId`, which every `rows` update invalidates.
179
+ - **`error` is final**, even after `renew()`. Fetch a fresh sealed query and subscribe again, with a new client if the connection failed. Watch `onStateChange` on direct subscriptions; `useLiveQuery()` reports errors through `status`, not by throwing or suspending.
180
+ - **When the session changes, leave with a full page load.** It drops the Realtime client, any TanStack `DbClient` and the previous user's rows. The hook retries a failed renewal every second, so stop renewing once one fails as signed out. Check on every renewal and write that the session's user is the one the page was loaded for, or a write from the old page lands in the new user's account. See [When the session changes](references/ssr.md#when-the-session-changes).
181
+
182
+ ## Optimistic Writes
183
+
184
+ Realtime has no write path, so writes keep your backend's usual auth. Read [references/mutations.md](references/mutations.md) before writing mutations, for each layer.
185
+
186
+ - [Return the txid from the write itself](references/mutations.md#returning-the-txid-from-your-backend): `` .returning({ txid: sql<string>`pg_current_xact_id()::text` }) ``, in one round trip on any Postgres driver. Write a batch in one statement, so there's one txid. Never open `db.transaction()` just to `select pg_current_xact_id()`: that's four round trips holding a pooled connection. When a write genuinely needs several statements, put the txid in the `RETURNING` of a write that always changes a row.
187
+ - Always pass `awaitTxId()` a timeout: it keeps waiting through reconnects and renewals, and never settles if the txid doesn't reach the query.
188
+ - A write that changes no rows has no txid, so skip `awaitTxId()` and let the optimistic state drop.
189
+
190
+ ## Server-Side Rendering
191
+
192
+ Read [references/ssr.md](references/ssr.md) before rendering live queries on the server, for Next.js and TanStack Start.
193
+
194
+ - Create the client in a `useState` initializer rather than at module level, render `initialData` as live rows without a loading state, and never share a TanStack `DbClient` between requests.
195
+ - Keep row shapes identical on both sides. `JSON.stringify` turns dates into strings, and `int8` and `numeric` arrive as strings.
196
+
197
+ ## More Docs
198
+
199
+ | Task | Page |
200
+ | --- | --- |
201
+ | Beta scope, regions, pricing, FAQ, other systems | [Overview](https://neon.com/docs/realtime/overview.md) |
202
+ | Architecture and consistency | [How it works](https://neon.com/docs/realtime/how-it-works.md) |
203
+ | A first live query | [Quickstart](https://neon.com/docs/realtime/quickstart.md) |
204
+ | Every SDK export | [TypeScript SDK reference](https://neon.com/docs/realtime/sdks/typescript.md) |
205
+ | Full-stack apps with auth and SSR | [Next.js](https://neon.com/docs/realtime/examples/nextjs.md), [TanStack](https://neon.com/docs/realtime/examples/tanstack.md) |
206
+ | Live agent workflows | [Mastra](https://neon.com/docs/realtime/examples/mastra.md) |
207
+ | ORM adapters | [Drizzle](https://neon.com/docs/realtime/examples/drizzle.md), [Kysely](https://neon.com/docs/realtime/examples/kysely.md) |