feat: split stream_id into stream_type + stream_id columns #191

Closed
opened 2026-03-06 11:46:17 +00:00 by ash · 0 comments
Owner

Problem

Streams are identified by a single stream_id string like "pass-template-123". The stream type is baked into a naming convention that cannot be queried efficiently, requires string concatenation (allocation), and is ambiguous.

Solution

Split into two first-class columns: stream_type + stream_id.

API changes

Decider gets StreamType field (required, panic if empty):

var decider = eskit.Decider[state, CreatePassTemplate, any]{
    StreamType: "pass-template",
}
func (c CreatePassTemplate) StreamID() string { return c.TemplateID }

Event struct gets StreamType:

type Event[E any] struct {
    StreamType     string
    StreamID       string
    Version        int
    GlobalSequence uint64
}

EventStore interface uses two args:

Load(ctx, streamType, streamID string) ([]Event[E], error)
Append(ctx, streamType, streamID string, expectedVersion int, events []E, ...) ([]Event[E], error)

New capability - type-filtered global reads:

ReadByStreamType(ctx, streamType string, from uint64, limit int) ([]Event[E], error)

Performance

  • Write: smaller total row size (two short strings vs one concatenated)
  • Read (load): composite index on short keys, equivalent or faster
  • Read (by type): NEW index-only scan, impossible today
  • In-memory: no string concatenation in StreamID(), zero allocation

Schema

stream_type TEXT NOT NULL,
stream_id   TEXT NOT NULL,
UNIQUE(stream_type, stream_id, version)
CREATE INDEX idx_events_stream ON events(stream_type, stream_id, version);
CREATE INDEX idx_events_by_type ON events(stream_type, id);

Scope

  1. Core: Event struct, EventStore interface, Decider
  2. Stores: pgstore, sqlitestore, memorystore
  3. Subscription, Processor, sqlview, pgview
  4. All tests and examples
  5. Benchmarks proving no regression
  6. Migration path for existing databases
## Problem Streams are identified by a single stream_id string like "pass-template-123". The stream type is baked into a naming convention that cannot be queried efficiently, requires string concatenation (allocation), and is ambiguous. ## Solution Split into two first-class columns: stream_type + stream_id. ### API changes Decider gets StreamType field (required, panic if empty): ```go var decider = eskit.Decider[state, CreatePassTemplate, any]{ StreamType: "pass-template", } func (c CreatePassTemplate) StreamID() string { return c.TemplateID } ``` Event struct gets StreamType: ```go type Event[E any] struct { StreamType string StreamID string Version int GlobalSequence uint64 } ``` EventStore interface uses two args: ```go Load(ctx, streamType, streamID string) ([]Event[E], error) Append(ctx, streamType, streamID string, expectedVersion int, events []E, ...) ([]Event[E], error) ``` New capability - type-filtered global reads: ```go ReadByStreamType(ctx, streamType string, from uint64, limit int) ([]Event[E], error) ``` ### Performance - Write: smaller total row size (two short strings vs one concatenated) - Read (load): composite index on short keys, equivalent or faster - Read (by type): NEW index-only scan, impossible today - In-memory: no string concatenation in StreamID(), zero allocation ### Schema ```sql stream_type TEXT NOT NULL, stream_id TEXT NOT NULL, UNIQUE(stream_type, stream_id, version) CREATE INDEX idx_events_stream ON events(stream_type, stream_id, version); CREATE INDEX idx_events_by_type ON events(stream_type, id); ``` ### Scope 1. Core: Event struct, EventStore interface, Decider 2. Stores: pgstore, sqlitestore, memorystore 3. Subscription, Processor, sqlview, pgview 4. All tests and examples 5. Benchmarks proving no regression 6. Migration path for existing databases
ash closed this issue 2026-03-06 20:32:16 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ash/eskit#191
No description provided.