# "CDC"
_Path: en/lua/storage/cdc_
> "
Subscribe to Postgres Change Data Capture streams from [`db.cdc.postgres`](system/cdc.md) sources. List configured sources, open a stream, and receive row-level change events over a channel.
## Loading
```lua
local cdc = require("cdc")
```
## list_sources
List all configured CDC sources:
```lua
local sources, err = cdc.list_sources()
for _, s in ipairs(sources) do
print(s.name, s.slot, s.streaming)
end
```
Each source is a table: `name`, `slot`, `publication`, `tables`, `streaming`, `failover`, `temporary`, `snapshot`. See [CDC sources](system/cdc.md#source-info).
**Returns:** `table, error`
## source
Get a single source by name (its entry ID):
```lua
local info, err = cdc.source("app:pg_cdc")
if info == nil then
-- no such source
end
```
**Returns:** `table, error` (source info, or `nil` if not found)
## stream
Open a change stream on a source. Returns a `cdc.Stream` whose channel delivers change events:
```lua
local stream, err = cdc.stream("app:pg_cdc", {
tables = { "public.users", "public.orders" },
ops = { "insert", "update" },
buffer = 128,
})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `name` | string | Source name (entry ID) |
| `opts.tables` | []string | Filter to these tables (omit for all configured tables) |
| `opts.ops` | []string | Filter to these operations: `insert`, `update`, `delete`, `truncate` |
| `opts.buffer` | int | Channel buffer size (1-65536) |
**Returns:** `Stream, error`
### channel
Return the channel that receives change events. The first call subscribes to the source (yields); subsequent calls return the same channel. `:receive()` blocks until the next change arrives, or returns `nil` when the stream ends:
```lua
local stream = cdc.stream("app:pg_cdc")
local ch = stream:channel()
while true do
local change = ch:receive()
if change == nil then break end -- stream closed
if change.op == "insert" then
handle_new_user(change.table, change.after)
elseif change.op == "update" then
handle_update(change.table, change.before, change.after)
elseif change.op == "delete" then
handle_delete(change.table, change.before)
end
end
```
`receive` is an alias for `channel`.
### close
Stop the subscription and release the stream. Idempotent; also auto-closed at task scope. `release` is an alias for `close`.
```lua
stream:close()
```
## Change Event
Each message received on the channel is a change table:
| Field | Description |
|-------|-------------|
| `op` | Operation: `insert`, `update`, `delete`, `truncate`, or `r` (snapshot read) |
| `schema` | Table schema |
| `table` | Table name |
| `relation` | `schema.table` |
| `before` | Row state before the change (`update`, `delete`; absent for `insert`) |
| `after` | Row state after the change (`insert`, `update`, `r`; absent for `delete`) |
| `source` | Source name |
| `lsn` | Log sequence number of the change |
| `commit_lsn` | LSN of the committing transaction (when applicable) |
| `xid` | Transaction ID (when applicable) |
`before` and `after` are row maps keyed by column name.
## Errors
| Condition | Kind |
|-----------|------|
| No context / no process PID | `errors.INTERNAL` |
| Source name required | `errors.INVALID` |
| Invalid buffer size | `errors.INVALID` |
| Source not found | `errors.INTERNAL` |
See [Error Handling](lua/core/errors.md) for working with errors.
## See Also
- [Change Data Capture](system/cdc.md) - `db.cdc.postgres` source configuration
- [Channel](lua/core/channel.md) - Channel semantics
- [Database](system/database.md) - SQL database services
## Navigation
Previous: "Message Queue" (lua/storage/queue)
Next: "System" (lua/system/system)