Skip to content

Consolidate IndexerService subscription stream to single-connection pattern (like ArkService #829) #950

@Kukks

Description

@Kukks

Context

PR #829 introduced a cleaner event stream pattern for ArkService.GetEventStream:

  • Client opens one stream connection
  • Server immediately emits StreamStartedEvent with a stream ID
  • Client uses UpdateStreamTopics (unary RPC) to dynamically add/remove/overwrite topics on that stream
  • Result: one connection to manage instead of juggling subscribe-then-stream choreography

The IndexerService in indexer.proto currently uses an older multi-step pattern:

  1. Client calls SubscribeForScripts (unary) → gets back a subscription_id
  2. Client opens GetSubscription(subscription_id) (server stream) → receives events
  3. To modify scripts mid-stream, client calls SubscribeForScripts again with the same subscription_id to add, or UnsubscribeForScripts to remove

This requires the client to manage the lifecycle of subscription creation before it can open the stream, and uses two separate unary RPCs for what is conceptually add/remove.

Proposal

Align IndexerService with the ArkService pattern from #829no breaking changes to existing RPCs.

1. Make subscription_id optional in GetSubscriptionRequest

message GetSubscriptionRequest {
  // If empty, server creates a new subscription automatically.
  // Existing clients can still pass a pre-created subscription_id.
  string subscription_id = 1;
  // Optional: scripts to subscribe to on stream creation.
  // Ignored if subscription_id already has scripts.
  repeated string scripts = 2;
}

When subscription_id is empty, the server creates a subscription internally, just like GetEventStream creates a listener. This lets new clients skip the SubscribeForScripts bootstrapping call entirely.

The optional scripts field lets the client subscribe to an initial set of scripts in the same call — single step to go from nothing to a fully active, filtered stream.

2. Add SubscriptionStartedEvent to the stream response

message GetSubscriptionResponse {
  oneof data {
    IndexerHeartbeat heartbeat = 1;
    IndexerSubscriptionEvent event = 2;
    SubscriptionStartedEvent subscription_started = 3; // NEW
  }
}

message SubscriptionStartedEvent {
  string subscription_id = 1;
}

Emitted immediately upon stream creation (mirrors StreamStartedEvent). The client now has the subscription ID without needing to call SubscribeForScripts first.

3. (Optional) Add UpdateSubscriptionScripts unary RPC

// Mirrors UpdateStreamTopics from ArkService
rpc UpdateSubscriptionScripts(UpdateSubscriptionScriptsRequest) returns (UpdateSubscriptionScriptsResponse) {
  option (meshapi.gateway.http) = {
    post: "/v1/indexer/script/updateScripts"
    body: "*"
  };
};

message UpdateSubscriptionScriptsRequest {
  string subscription_id = 1;
  oneof scripts_change {
    ModifyScripts modify = 2;
    OverwriteScripts overwrite = 3;
  }
}

message ModifyScripts {
  repeated string add_scripts = 1;
  repeated string remove_scripts = 2;
}

message OverwriteScripts {
  repeated string scripts = 1;
}

message UpdateSubscriptionScriptsResponse {
  repeated string scripts_added = 1;
  repeated string scripts_removed = 2;
  repeated string all_scripts = 3;
}

This consolidates add/remove into a single RPC with oneof semantics, matching the UpdateStreamTopics pattern. The existing SubscribeForScripts and UnsubscribeForScripts RPCs continue to work unchanged.

Backward compatibility

Client What changes?
Old clients Nothing. SubscribeForScriptsGetSubscription(id)UnsubscribeForScripts all remain and work identically.
New clients GetSubscription() (no ID, optional initial scripts) → receive SubscriptionStartedEventUpdateSubscriptionScripts to modify. Single connection, cleaner lifecycle.

Migration path

Once new clients have adopted the consolidated pattern, the old SubscribeForScripts / UnsubscribeForScripts RPCs can be deprecated (but not removed) in a future version.

Related

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions