Domain Model
Signal defines no schema of its own. Its only persisted entity is
ActivityNotification, whose table, model, and repository all live in@nx/core. Connection and AES-key state are held in-memory per instance and are not modeled here.
1. Full ERD
ActivityNotification has no foreign-key relations in the schema — recipientId and organizerId are soft references to user / organizer IDs resolved at write time.
2. Entities
ActivityNotification
| Property | Value |
|---|---|
| Table | ActivityNotification |
| Source | packages/core/src/models/schemas/public/activity-notification/schema.ts |
| Model | packages/core/src/models/schemas/public/activity-notification/model.ts |
| Repository | ActivityNotificationRepository (@nx/core) |
| Soft-delete | yes (SoftDeletableRepository, deletedAt) |
| Owner ID column | recipientId (per-user) |
Fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
recipientId | text | ✓ | — | Target user; one row per recipient |
type | varchar(80) | ✓ | — | TActivityNotificationTypes (see enum) |
organizerId | text | null | Owning organizer, if scoped | |
content | text | ✓ | — | Rendered plain-text content |
html | text | ✓ | — | Rendered HTML content |
actionUrl | text | null | Deep-link target | |
data | jsonb | ✓ | {} | Actor + event payload snapshot |
isRead | boolean | ✓ | false | Read flag |
readAt | timestamptz | null | Set when marked read |
Type enum (ActivityNotificationTypes):
| Value | Description |
|---|---|
PAYMENT_SUCCESS | Only type implemented today; rendered from the actor + payment payload |
The worker rejects any
eventTypenot inActivityNotificationTypes.SCHEME_SET(logs a warning and skips).
Indexes:
| Name | Columns | Purpose |
|---|---|---|
IDX_ActivityNotification_recipientId_isDeleted_createdAt | recipient_id, deleted_at, created_at | Bell query — recipient's notifications, newest first |
IDX_ActivityNotification_recipientId_isRead | recipient_id, is_read | Unread count |
IDX_ActivityNotification_organizerId_type_createdAt | organizer_id, type, created_at | Filter by organizer + type |
Relations: none declared in schema (recipientId / organizerId are soft references).
3. Cross-entity Invariants
| Invariant | Enforcement |
|---|---|
| One row per recipient per fan-out | Worker loops over resolved recipients and createAlls a row each |
readAt set ⇔ isRead = true | ActivityNotificationService.markAsRead / markAllAsRead set both together |
| Recipient can only read/mutate their own rows | Controller scopes every query by recipientId = JWT subject |
4. Soft-delete Behavior
| Behavior | Detail |
|---|---|
| Read default | deletedAt IS NULL (model defaultFilter) |
| Hard-delete | not used by signal |
| Restore | not exposed |