Skip to content

feat: first-class topic registry#462

Merged
pratyush618 merged 11 commits into
masterfrom
feat/topics-registry
Jul 19, 2026
Merged

feat: first-class topic registry#462
pratyush618 merged 11 commits into
masterfrom
feat/topics-registry

Conversation

@pratyush618

@pratyush618 pratyush618 commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

What

Adds a first-class topic registry — the second S28 log+cursor pub/sub follow-up. Declaring a topic lets its log messages be retained even with zero subscribers at publish time, removing the late-join boundary, with an optional per-topic retention window.

queue.declare_topic("orders", retention=3600)  # keep up to 1 hour

queue.publish("orders", 42)              # retained with no subscriber yet
queue.subscribe_log("orders", "audit-log")
queue.read_topic("orders", "audit-log")  # -> sees the earlier publish

Core

  • Migration m0006topics table (name PK, mode default 'log', retention_ms, created_at).
  • Storage: declare_topic (idempotent upsert), get_topic, list_declared_topics — SQLite, Postgres, Redis (a topics hash).
  • publish_to_topic writes a log row when the topic is a declared log topic or a log subscriber exists (was: only the latter). The registry lookup runs only when there is no log subscriber, so the hot path is unchanged. A declared topic with retention_ms stamps expires_at = now + retention_ms on the message, so the existing expiry sweep reclaims sub-less messages — no sweep change.

Cross-SDK

SDK API
Python queue.declare_topic(name, *, retention=<seconds>) / list_declared_topics()
Node queue.declareTopic(name, { retention }) / listDeclaredTopics()
Java taskito.declareTopic(name[, Duration retention]) / listDeclaredTopics()

Tests

  • Rust contract suite (test_topic_registry): declare → publish with zero subs → late subscribe_log reads it; retention round-trips; idempotent re-declare preserves created_at. SQLite green; Postgres/Redis run in CI.
  • Python (3), Node (2), Java (TopicRegistryTest, 2) declare-then-late-join round-trips. Full suites green.
  • BINDING_CONTRACT.md + shared guide "Declared topics" section + 3 API refs.

Summary by CodeRabbit

  • New Features

    • Added a durable log-topic registry with configurable bounded or unbounded retention.
    • Added topic-management APIs to declare and list declared topics across Java, Node.js, and Python.
    • Published log messages are retained for late log subscribers when the topic is declared (even with zero subscribers).
    • Re-declaring a topic updates retention while preserving its original creation time.
  • Documentation

    • Updated the Pub/Sub API docs to cover topic declaration, retention, and listing.
  • Tests

    • Added backend-agnostic and SDK-level test coverage for registry listing, late-join retention behavior, and idempotent redeclaration.

Declared log topics retain publishes with zero subscribers, with an optional per-topic retention window; removes the S28 late-join boundary.
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 932edb03-7f43-436a-a054-23ae55b7827b

📥 Commits

Reviewing files that changed from the base of the PR and between 10aa3c7 and 0016d6e.

📒 Files selected for processing (9)
  • crates/taskito-core/src/pubsub.rs
  • crates/taskito-core/src/storage/postgres/pubsub.rs
  • crates/taskito-core/src/storage/redis_backend/pubsub.rs
  • crates/taskito-core/src/storage/sqlite/pubsub.rs
  • crates/taskito-core/src/storage/traits.rs
  • docs/content/docs/shared/guides/core/pubsub.mdx
  • sdks/java/src/test/java/org/byteveda/taskito/core/LogConsumerTest.java
  • sdks/node/test/core/pubsub-log.test.ts
  • sdks/python/tests/core/test_pubsub_log.py
📝 Walkthrough

Walkthrough

Adds a declared-topic registry across storage backends, retains log publishes for declared topics without subscribers, and exposes declaration/listing APIs through Java, Node, and Python bindings with tests and documentation.

Changes

Declared topic registry

Layer / File(s) Summary
Registry contract and data model
crates/taskito-core/BINDING_CONTRACT.md, crates/taskito-core/migrations/..., crates/taskito-core/src/storage/...
Defines topic metadata, storage methods, Diesel schema/models, migration, and topic record conversion.
Backend persistence and dispatch
crates/taskito-core/src/storage/..., crates/taskito-core/tests/rust/storage_tests.rs
Adds idempotent topic upserts and lookup/list operations for Diesel and Redis, wires them through storage wrappers, and tests registry behavior.
Retention-aware log publishing
crates/taskito-core/src/pubsub.rs
Retains messages for declared log topics without active log subscribers and validates expiry and redeclaration semantics.
Cross-language APIs and validation
crates/taskito-java/..., crates/taskito-node/..., crates/taskito-python/..., sdks/...
Adds topic declaration/listing APIs, typed topic views, JNI/N-API/Python bridges, and SDK integration tests.
API and behavior documentation
docs/content/docs/...
Documents declared-topic APIs, metadata, idempotency, and retention behavior across supported SDKs.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SDK
  participant NativeBindings
  participant Storage
  participant TopicRegistry
  participant TopicMessages

  SDK->>NativeBindings: declare topic
  NativeBindings->>Storage: declare_topic(name, mode, retention_ms)
  Storage->>TopicRegistry: upsert topic metadata
  SDK->>NativeBindings: publish before subscription
  NativeBindings->>Storage: publish_to_topic
  Storage->>TopicRegistry: get_topic
  Storage->>TopicMessages: store retained message
  SDK->>NativeBindings: list declared topics
  NativeBindings->>Storage: list_declared_topics
  Storage-->>SDK: topic metadata
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: a new first-class topic registry.
Docstring Coverage ✅ Passed Docstring coverage is 82.09% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/topics-registry

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/taskito-core/src/pubsub.rs`:
- Around line 475-494: Update
declare_topic_is_idempotent_and_preserves_created_at so the first and second
declare_topic calls occur in distinct milliseconds, waiting or otherwise
advancing the clock between them before asserting second.created_at equals
first.created_at.

In `@crates/taskito-core/src/storage/traits.rs`:
- Around line 293-300: Define the declare_topic contract in
crates/taskito-core/src/storage/traits.rs to accept only mode "log" and
non-negative retention_ms values. Enforce these invariants before persistence in
declare_topic for crates/taskito-core/src/storage/postgres/pubsub.rs (lines
14-30), crates/taskito-core/src/storage/sqlite/pubsub.rs (lines 14-30), and
crates/taskito-core/src/storage/redis_backend/pubsub.rs (lines 794-812),
rejecting invalid inputs before the upsert or hash write.

In `@docs/content/docs/shared/guides/core/pubsub.mdx`:
- Around line 501-506: Update the “Declared topics” explanation to qualify
late-join behavior by retention: declared topics retain publishes without an
existing durable log subscription, but finite retention may expire messages
before a later subscriber arrives, so the whole backlog is not guaranteed.
Replace “live log subscriber” terminology with explicit references to an
existing durable log subscription, independent of whether its consumer process
is online, and apply the same clarification to the corresponding text later in
the guide.

In `@sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java`:
- Around line 934-936: Reject negative retention values before declaring topics:
in sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java lines
934-936, validate the Duration in declareTopic before converting it to
milliseconds; in sdks/node/src/queue.ts lines 661-666, validate retention before
rounding and forwarding it. Preserve null/omitted retention behavior and use the
SDKs’ existing argument-validation error convention.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 535a4547-d257-44d8-ab33-c9e49001c63e

📥 Commits

Reviewing files that changed from the base of the PR and between 6b91592 and 73ff8f9.

📒 Files selected for processing (37)
  • crates/taskito-core/BINDING_CONTRACT.md
  • crates/taskito-core/migrations/m0006_topics.rs
  • crates/taskito-core/src/pubsub.rs
  • crates/taskito-core/src/storage/diesel_common/pubsub.rs
  • crates/taskito-core/src/storage/mod.rs
  • crates/taskito-core/src/storage/models.rs
  • crates/taskito-core/src/storage/postgres/pubsub.rs
  • crates/taskito-core/src/storage/records.rs
  • crates/taskito-core/src/storage/redis_backend/pubsub.rs
  • crates/taskito-core/src/storage/schema.rs
  • crates/taskito-core/src/storage/sqlite/pubsub.rs
  • crates/taskito-core/src/storage/traits.rs
  • crates/taskito-core/tests/rust/storage_tests.rs
  • crates/taskito-java/src/convert.rs
  • crates/taskito-java/src/queue/pubsub.rs
  • crates/taskito-node/src/convert/mod.rs
  • crates/taskito-node/src/convert/pubsub.rs
  • crates/taskito-node/src/queue/pubsub.rs
  • crates/taskito-python/src/py_queue/pubsub.rs
  • docs/content/docs/java/api-reference/queue/pubsub.mdx
  • docs/content/docs/node/api-reference/queue/pubsub.mdx
  • docs/content/docs/python/api-reference/queue/pubsub.mdx
  • docs/content/docs/shared/guides/core/pubsub.mdx
  • sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java
  • sdks/java/src/main/java/org/byteveda/taskito/Taskito.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/JniQueueBackend.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/NativeQueue.java
  • sdks/java/src/main/java/org/byteveda/taskito/model/Topic.java
  • sdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.java
  • sdks/java/src/test/java/org/byteveda/taskito/core/TopicRegistryTest.java
  • sdks/node/src/native.ts
  • sdks/node/src/queue.ts
  • sdks/node/src/types.ts
  • sdks/node/test/core/topics-registry.test.ts
  • sdks/python/taskito/_taskito.pyi
  • sdks/python/taskito/mixins/pubsub.py
  • sdks/python/tests/core/test_pubsub_log.py

Comment thread crates/taskito-core/src/pubsub.rs
Comment thread crates/taskito-core/src/storage/traits.rs
Comment thread docs/content/docs/shared/guides/core/pubsub.mdx Outdated
Comment thread sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java
# Conflicts:
#	docs/content/docs/java/api-reference/queue/pubsub.mdx
#	docs/content/docs/node/api-reference/queue/pubsub.mdx
#	docs/content/docs/python/api-reference/queue/pubsub.mdx
#	sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java
#	sdks/java/src/main/java/org/byteveda/taskito/Taskito.java
#	sdks/python/taskito/mixins/pubsub.py
declare_topic now rejects a non-"log" mode and a negative retention_ms (which would expire messages immediately or overflow now+retention) in every backend; the created_at test sleeps so preservation is observable.
@pratyush618

Copy link
Copy Markdown
Collaborator Author

Addressed the CodeRabbit review (commits ac3bbd26, 50c61fa6), and merged in the newly-landed master (PR #461).

  • Core validationdeclare_topic now rejects a non-"log" mode and a negative retention_ms (which would expire messages immediately or overflow now + retention) in every backend, via a shared validate_topic_declaration. The trait documents the invariant; a new test asserts both rejections.
  • Test — the created_at-preservation test sleeps 2ms between declarations so a regression that overwrote created_at would actually fail.
  • Docs — the "Declared topics" guide now qualifies the late-join guarantee (a late subscriber sees the backlog still within the retention window; expired messages are gone) and says "durable log subscription" rather than "live subscriber".

The cold macOS/Windows smoke runners starved on five per-file worker startups (a 40s waitFor still timed out). Four consumers now share one worker, so once it's up they all drain — one startup instead of four.
Same cold-runner robustness as the Node suite: four consumers share one worker instead of one worker lifecycle per test.
Consolidate the three managed-consumer cases into one worker so the cold-compile Java smoke jobs don't starve on repeated worker startups.
@pratyush618
pratyush618 merged commit d120107 into master Jul 19, 2026
32 checks passed
@pratyush618
pratyush618 deleted the feat/topics-registry branch July 19, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant