# Topic

## Purpose

The Topic module provides a store for named topics used in message routing. A topic is a name paired with a generated identifier; channels subscribe to topics and receive the messages routed to them.

---

## Key Interfaces

### `Topic`

```typescript
interface Topic {
  readonly name: string // Human-readable topic name
  readonly id: string // Unique identifier (UUID v4, generated by the store)
}
```

### `TopicStore`

```typescript
interface TopicStore {
  readonly create: (...name: string[]) => void // Creates topics by name with generated ids
  readonly add: (...topic: Topic[]) => void // Adds existing topic objects
  readonly existsByName: (name: string) => boolean
  readonly existsById: (id: string) => boolean
  readonly getByName: (name: string) => Topic | null
  readonly getById: (id: string) => Topic | null
  readonly list: readonly Topic[]
  readonly removeByName: (...name: string[]) => void
  readonly removeById: (...id: string[]) => void
  readonly clear: () => void
}
```

---

## Factory Functions

### `createTopicStore`

**Location**: [`@hyperfrontend/network-protocol/topic`](https://www.hyperfrontend.dev/docs/libraries/network-protocol/topic.md)

```typescript
function createTopicStore(): TopicStore
```

---

## Usage Examples

### Basic Topic Management

```typescript
import { createTopicStore } from '@hyperfrontend/network-protocol/topic'

const topicStore = createTopicStore()

topicStore.create('user-events', 'system-events', 'notifications')

const userEvents = topicStore.getByName('user-events')
// => { name: 'user-events', id: '<generated uuid>' }

topicStore.existsByName('user-events') // true
topicStore.list.length // 3
```

### Adding Existing Topics

```typescript
topicStore.add({ name: 'chat.messages', id: chatTopicId })
topicStore.getById(chatTopicId)
```

The id is not validated by [`add`](https://www.hyperfrontend.dev/docs/libraries/network-protocol/topic/#api-TopicStore-prop-add), but every routed-packet creator requires a UUID v4 topic id (see [`routing/`](https://www.hyperfrontend.dev/docs/libraries/network-protocol/routing.md)), so use one.

### Topic Removal

```typescript
topicStore.removeByName('user-events', 'system-events')
topicStore.removeById(chatTopicId)
topicStore.clear()
topicStore.list.length // 0
```

---

## Error Handling

```typescript
topicStore.create('user-events') // twice
// Error: "Cannot create a topic with name 'user-events' as a topic with that name already exists"

topicStore.create('')
// Error: "Invalid topic name ''"

topicStore.add({ name: 'user-events', id })
// Error: "Cannot add a topic with name 'user-events' as it already exists"

topicStore.removeByName('missing')
// Error: "No topic found with name 'missing' to remove"

topicStore.removeById('missing')
// Error: "No topic found with id 'missing' to remove"
```

---

## Topic Naming Conventions

| Pattern           | Example           | Use Case                   |
| ----------------- | ----------------- | -------------------------- |
| `entity-action`   | `user-created`    | Entity lifecycle events    |
| `domain-events`   | `payment-events`  | Domain-specific events     |
| `system-category` | `system-metrics`  | System-level notifications |
| `'feature.sub'`   | `'chat.messages'` | Hierarchical namespacing   |

---

## Integration with Routing

```typescript
import { createTopicStore } from '@hyperfrontend/network-protocol/topic'
import { createChannelStore } from '@hyperfrontend/network-protocol/browser/channel'

const topicStore = createTopicStore()
const channelStore = createChannelStore()

topicStore.create('user-events', 'system-events')

const first = channelStore.create('client-1', {
  send,
  receive,
  protocolProvider,
  session: firstSession,
})

const router: Router = (channels, topics) => {
  const subscriptions = new WeakMap<Channel, Topic[]>()
  const userEvents = topics.find((t) => t.name === 'user-events')
  subscriptions.set(first, userEvents ? [userEvents] : [])
  return { isDynamic: false, subscriptions }
}

const routingOptions = router(
  channelStore.list.map((entry) => entry.channel),
  topicStore.list
)
```

---

## Validation Helpers

`validations/` holds [`isValidName`](https://www.hyperfrontend.dev/docs/libraries/network-protocol/#api-isValidName) (a non-empty string), [`isValidTopicId`](https://github.com/AndrewRedican/hyperfrontend/blob/main/libs/network-protocol/src/lib/topic/validations/is-valid-topic-id.ts) (a 36-character UUID v4 string), and [`isValidTopic`](https://github.com/AndrewRedican/hyperfrontend/blob/main/libs/network-protocol/src/lib/topic/validations/is-valid-topic.ts) (both). [`isValidTopicId`](https://github.com/AndrewRedican/hyperfrontend/blob/main/libs/network-protocol/src/lib/topic/validations/is-valid-topic-id.ts) is what the routing creators use to check a topic id.

---

## Relationship to Other Modules

- **Depends on**: [`@hyperfrontend/random-generator-utils`](https://www.hyperfrontend.dev/docs/libraries/utils/random-generator.md) (identifiers)
- **Used by**: [`routing/`](https://www.hyperfrontend.dev/docs/libraries/network-protocol/routing.md)

---

## See Also

- **[Library Index](../README.md)** - All modules
- **[Architecture Guide](../../../ARCHITECTURE.md#topic)** - Topic architecture
- **[Topic Entry](../../topic/README.md)** - The [`@hyperfrontend/network-protocol/topic`](https://www.hyperfrontend.dev/docs/libraries/network-protocol/topic.md) entry

### Related Modules

| Module                           | Relationship                         |
| -------------------------------- | ------------------------------------ |
| [routing/](https://www.hyperfrontend.dev/docs/libraries/network-protocol/routing.md) | Uses topics for message distribution |

---

Canonical page: https://www.hyperfrontend.dev/docs/libraries/network-protocol/topic/
This file: https://www.hyperfrontend.dev/docs/libraries/network-protocol/topic.md
Documentation index: https://www.hyperfrontend.dev/llms.txt
