_id, _CHANGE_TYPE, and a _CHANGE_SEQUENCE_NUMBER from one gap-free, producer-wide counter, so a subscriber holding a contiguous run of sequence numbers is missing no operation the producer committed inside it. Nothing but row changes goes on the topic, so a direct BigQuery subscription needs no message filter.
@google-cloud/pubsub is an optional peer dependency. The default SQLite-backed state additionally needs better-sqlite3:
pubsubTarget
TopicRoute<Data>:
MessageDraft:
Fork handling
On a reorg, the target folds every orphaned id in the rollback manifest back to the state that survives at the safe cursor, enqueues the resulting compensations (adelete, or the route’s rollbackWhenMissing result), and rewinds before the source re-streams the canonical blocks. The finalized head a consumer needs for its own confirmation policy rides the _finalized attribute on every message; it bounds retraction, not arrival, since a pipe still catching up publishes well below it. See Fork handling for how the finalization watermark is resolved and enforced across targets.
windowTopic
TopicRoute for an aggregator window stream: every re-emission of a window is an upsert on the same id (mode: 'materialized'), so BigQuery CDC replaces the previous revision instead of appending a new row.
SqlitePubsubState
pubsubTarget constructs when state is given as { path }. id overrides the cursor key that would otherwise bind to the pipe’s own id once the target opens the store. To use a different backend, implement the PubsubState interface (open, getCursor, getMeta, setMeta, commit, pending, confirm, fork, stats, close) and pass the instance as state directly.
Example
windowTopic and a direct BigQuery subscription: 18.pubsub.example.ts.
Notes
- The canonical codec encodes a
bigintas a decimal string and aDateas RFC 3339; a value it cannot represent throwsPubsubTargetError(E2405), and a cycle throwsE2406. - Every error the target,
windowTopic, or the SQLite state can raise is aPubsubTargetErrorwith a stableE24xxcode. See the error reference for the full list and fixes. - The state file is the producer’s sequencer: keep it on durable storage and run one producer per path. Losing it and restarting under the same namespace is a
COLD_START_REFUSED(E2420), not something to retry past. Recovery is a fresh namespace and a consumer re-bootstrap. - A state file written by
1.0.0-beta.4is upgraded in place on first open and keeps its change sequence, so consumers need no re-bootstrap. It is refused withE2411while its outbox still holds signal operations: let1.0.0-beta.4drain the outbox, then upgrade. - When the finalized head advances, a batch only records how far finality has moved. Finalized rows are hidden from every read at once and deleted later, 1,000 rows per transaction, between batches, so a large state no longer stalls publishing and health checks. If the pipe stops first, the cleanup continues on the next start. To downgrade to an older SDK version, stop the pipe only when
SELECT COUNT(*) FROM manifest WHERE block_number <= (SELECT CAST(value AS INTEGER) FROM meta WHERE key = 'folded_through')returns 0 on the state file. Until then it can hold finalized rows that the older version reads as live.