openvidu/openvidu-dashboard/openspec/specs/data-model/spec.md

30 KiB

data-model Specification

Purpose

The analytics read model stored in MongoDB: the collections, fields, indexes, retention and versioning contract shared by the OpenVidu server (writer) and OpenVidu Dashboard (reader), and the write rules that keep the model consistent when events are delayed, replayed or duplicated.

Requirements

Requirement: Schema version contract

Edition: CE and PRO. The writer SHALL record the schema version it implements in schema_meta (_id: "schema", integer version, writerVersion, updatedAt) at startup. The dashboard SHALL compare that version with the one it supports and refuse to serve data when the writer is older than its minimum.

Scenario: Writer writes its version

  • GIVEN a server with analytics enabled starting against an empty database
  • WHEN it finishes initialization
  • THEN schema_meta contains { _id: "schema", version: <writer schema version> } and the collections and indexes of this specification exist

Scenario: Dashboard rejects an older writer

  • GIVEN schema_meta.version lower than the dashboard's minimum supported version
  • WHEN the dashboard starts
  • THEN it exits with a message naming both versions and the server version to install

Scenario: Dashboard tolerates a newer writer

  • GIVEN schema_meta.version higher than the dashboard's version
  • WHEN the dashboard starts
  • THEN it serves data and GET /api/v1/info reports the mismatch as a warning

Requirement: Writer metadata documents

Edition: CE and PRO. Besides schema, schema_meta SHALL hold retention, capabilities and config, which the writer replaces at every initialization, migration (server-legacy-migration) and reconciler (server-reconciler); _id: "dashboard" is reserved for non-secret facts written by the dashboard.

Scenario: Retention document

  • GIVEN a writer initialized with the default retention
  • WHEN schema_meta _id: "retention" is read
  • THEN it has eventsMs, statsMs, tracksMs and rollups5MinMs 604 800 000, entitiesMs 7 776 000 000, rollupsHourlyMs 34 560 000 000, rollupsDailyMs: null (daily rollups kept forever) and updatedAt

Scenario: Daily rollup retention recorded

  • GIVEN a writer initialized with retention.rollups_daily: 1825d
  • WHEN schema_meta _id: "retention" is read
  • THEN rollupsDailyMs is 157 680 000 000

Scenario: Capabilities and config documents

  • WHEN schema_meta _id: "capabilities" and _id: "config" are read
  • THEN capabilities has timeseries, transactions, serverVersion and detectedAt, and config has the writer's statsDetail (participant | track) and recordedAt

Requirement: Entity documents keyed by LiveKit identifiers

Edition: CE and PRO. Each room session, participant session, published track, subscription, egress, ingress and ingress session SHALL be represented by exactly one document whose _id is its LiveKit identifier (RM_…, PA_…, TR_…, <trackSid>:<subscriberSid>, EG_…, IN_…, ingress resource_id), created by the first event that mentions it and updated in place by later events.

Scenario: Events out of order

  • GIVEN a PARTICIPANT_ACTIVE event processed before its PARTICIPANT_JOINED
  • WHEN both have been processed in any order
  • THEN there is one participants document with status: "active", joinedAt from JOINED and activeAt from ACTIVE

Scenario: Replayed event

  • GIVEN a document already updated by an event
  • WHEN the same event (same LiveKit event id) is processed again
  • THEN the document is unchanged and no counter has moved

Requirement: Writer bookkeeping

Edition: CE and PRO. The set-based counters of rooms, participants, tracks and ingresses documents SHALL be derived from id sets kept under the writer-only internal field, so that a replayed or reordered event never counts twice; internal is not part of the read contract: the dashboard never reads it and the API never returns it.

Scenario: Session counted once

  • GIVEN a PARTICIPANT_JOINED of a STANDARD session already applied to its room
  • WHEN the same event is applied again in a later batch
  • THEN the room's participants.sessions is unchanged and equals the size of the STANDARD session id set under internal

Requirement: Room document

Edition: CE and PRO. A rooms document SHALL describe one room session: identity and lifecycle, node, configuration, participant/track/egress/ingress counters, traffic, quality, feature flags and retention fields, with the field list of the shared contract (dashboard-ce/typings/src/data-model). participants.sessions, unique, active, peakActive and aborted SHALL count STANDARD sessions only; participants.byKind SHALL count the sessions of every kind.

Scenario: Fields of a room document

  • WHEN a room document is read
  • THEN it has name, nameLower, status (active | closed), createdAt, endedAt, durationMs, endReason, closedBy, nodeId, config (max participants, empty and departure timeouts, metadata when allowed), participants { sessions, unique, active, peakActive, aborted, byKind }, tracks, egress (by type), ingress, bytes { up, down }, quality { scoreSum, scoreCount, minScore, poor, lost }, features { egress, ingress, agent, sip }, lastEventAt, lastStatsAt, mediaStats (only false, when its node does not report media statistics), schemaVersion, expiresAt

Scenario: Room lifecycle

  • GIVEN ROOM_CREATED, three PARTICIPANT_JOINED/ACTIVE, one PARTICIPANT_LEFT, ROOM_ENDED (API_DELETE)
  • WHEN all are processed
  • THEN the room has status: "closed", endReason: "API_DELETE", participants.sessions: 3, participants.active: 0, participants.peakActive: 3, durationMs = endedAt - createdAt, closedBy: "event"

Scenario: Dependent participants do not count as room size

  • GIVEN a room with two STANDARD participants and one EGRESS participant
  • WHEN counters are read
  • THEN participants.byKind.EGRESS is 1 and participants.active/peakActive count only the two STANDARD participants

Scenario: Only standard participants count as participants

  • GIVEN an active room with two active STANDARD participants of different identities, one EGRESS, one INGRESS and one AGENT participant, and one aborted SIP session
  • WHEN counters are read
  • THEN participants.sessions is 2, unique is 2, active is 2, peakActive is 2, aborted is 0 and participants.byKind is { STANDARD: 2, EGRESS: 1, INGRESS: 1, AGENT: 1, SIP: 1 }

Requirement: Participant session document

Edition: CE and PRO. A participants document SHALL represent one participant session (one SID) with its identity, room, kind, lifecycle status, connection facts, client facts, track and subscription counters, traffic, quality and retention fields, with the field list of the shared contract.

Scenario: Fields of a participant document

  • WHEN a participant document is read
  • THEN it has identity, identityLower, name, roomSid, roomName, kind, kindDetails, status (joining | active | left | aborted), joinedAt, activeAt, leftAt, durationMs, disconnectReason, closedBy, connectTimeMs, connectionType, region, nodeId, resumes, client { sdk, version, os, osVersion, browser, browserVersion, deviceModel, network, address }, permission, isPublisher, metadata and attributes (when allowed), tracks { published, bySource, subscribed, subscribeFailures }, bytes, quality, lastEventAt, lastStatsAt, mediaStats (only false, when its node does not report media statistics), schemaVersion, expiresAt

Scenario: Connection aborted

  • GIVEN PARTICIPANT_JOINED followed by PARTICIPANT_CONNECTION_ABORTED
  • WHEN both are processed
  • THEN the document has status: "aborted", activeAt absent, leftAt set and disconnectReason from the event

Scenario: Reconnect creates a new session

  • GIVEN a participant that reconnects with the same identity and receives a new SID
  • WHEN PARTICIPANT_LEFT (DUPLICATE_IDENTITY) and the new PARTICIPANT_JOINED are processed
  • THEN two documents exist with the same identity and roomSid, the first left and the second joining or active

Scenario: Resume keeps the session

  • GIVEN an active participant that resumes its signal connection
  • WHEN PARTICIPANT_RESUMED is processed
  • THEN the same document stays active and resumes increases by one

Requirement: Track and subscription documents

Edition: CE and PRO. A tracks document SHALL describe one published track (references, media facts, lifecycle, subscriber counters, final publish statistics) and a track_subscriptions document SHALL describe one (track, subscriber) pair (references, lifecycle, error, final subscribe statistics).

Scenario: Fields of track and subscription documents

  • WHEN a track and a subscription document are read
  • THEN the track has roomSid, participantSid, identity, type, source, mimeType, name, status (published | unpublished), publishedAt, unpublishedAt, durationMs, closedBy, muted, width, height, simulcast, codecs[], encryption, subscribers { current, total, failed }, publishStats; and the subscription has trackSid, roomSid, subscriberSid, subscriberIdentity, publisherSid, publisherIdentity, source, status (requested | subscribed | unsubscribed | failed), error, subscribedAt, unsubscribedAt, durationMs, closedBy, subscribeStats

Scenario: Track closed by participant leave

  • GIVEN a published track whose TRACK_UNPUBLISHED was never emitted
  • WHEN the publisher's PARTICIPANT_LEFT is processed
  • THEN the track becomes unpublished with unpublishedAt = leftAt and its open subscriptions become unsubscribed

Scenario: Subscription failure

  • GIVEN TRACK_SUBSCRIBE_REQUESTED then TRACK_SUBSCRIBE_FAILED with an error
  • WHEN both are processed
  • THEN the subscription has status: "failed" and error set, and the subscriber's tracks.subscribeFailures is 1

Requirement: Egress document

Edition: CE and PRO. An egresses document SHALL describe one egress: room reference, type, source, lifecycle status with history, timestamps, error, a sanitized request summary, the request in raw payload encoding and its outputs, with the field list of the shared contract. roomNameLower SHALL be the lower-cased roomName, written together with it.

Scenario: Fields of an egress document

  • WHEN an egress document is read
  • THEN it has roomSid, roomName, roomNameLower, type (room_composite | web | participant | track_composite | track), sourceType, status (starting | active | ending | complete | failed | aborted | limit_reached), closedBy, startedAt, updatedAt, endedAt, durationMs, error, errorCode, request (sanitized), rawRequest?, outputs[] { kind, name or url, location, size, durationMs, status, error, startedAt, endedAt }, statusHistory[] with at most 50 entries, retryCount, backupStorageUsed, schemaVersion, expiresAt

Scenario: Egress completes

  • GIVEN EGRESS_STARTED, two EGRESS_UPDATED and EGRESS_ENDED with status COMPLETE and one file result
  • WHEN all are processed
  • THEN the document has status: "complete", one output of kind file with its filename, location, size and duration, and four statusHistory entries

Scenario: Credentials are never stored

  • GIVEN an egress request with S3 credentials and a stream URL containing a key
  • WHEN the egress is projected
  • THEN neither request nor rawRequest contains a credential field, and the stream output URL has its key redacted

Scenario: Room name search field

  • GIVEN an egress of room Demo-Room
  • WHEN its document is read
  • THEN it has roomName: "Demo-Room" and roomNameLower: "demo-room"

Scenario: Web egress without room

  • GIVEN a web egress, which has no room
  • WHEN its document is read
  • THEN it has neither roomName nor roomNameLower

Requirement: Raw egress request

Edition: CE and PRO. rawRequest SHALL hold the request as the raw events of the egress store egress.request: the member of the request oneof that is set, under its protocol name, through the secrets allow-list.

Scenario: Raw request

  • GIVEN a room composite egress of room Demo-Room with layout grid, preset H264_720P_30, an MP4 file output with path recordings/demo.mp4 uploaded to S3 with credentials, and a stream output to rtmps://live.example.com/app/live_123?token=abc
  • WHEN its EGRESS_STARTED event is processed
  • THEN rawRequest is { room_composite: { room_name: "Demo-Room", layout: "grid", options: { preset: "H264_720P_30" }, file_outputs: [{ file_type: "MP4", filepath: "recordings/demo.mp4" }], stream_outputs: [{ urls: ["rtmps://live.example.com/app/***"] }] } }, the value of payload.egress.request in the stored event

Requirement: Ingress and ingress session documents

Edition: CE and PRO. An ingresses document SHALL describe one ingress definition and its aggregate session facts, and an ingress_sessions document SHALL describe one publishing session (resource_id) with its room, lifecycle and negotiated media parameters.

Scenario: Fields of ingress documents

  • WHEN an ingress and an ingress session document are read
  • THEN the ingress has name, nameLower, inputType (rtmp | whip | url), sanitized url, roomName, participantIdentity, participantName, reusable, enabled, enableTranscoding, createdAt, deletedAt, status, lastError, sessions, totalDurationMs, lastSessionAt; and the session has ingressId, ingressName, inputType, roomSid, roomName, participantIdentity, status, error, closedBy, startedAt, endedAt, durationMs, video { mimeType, averageBitrate, width, height, framerate }, audio { mimeType, averageBitrate, channels, sampleRate }

Scenario: Reusable ingress with two sessions

  • GIVEN INGRESS_CREATED, then INGRESS_STARTED/INGRESS_ENDED twice with different resource_id
  • WHEN all are processed
  • THEN one ingresses document has sessions: 2 and totalDurationMs equal to the sum of both sessions, and two ingress_sessions documents exist

Scenario: Stream key is never stored

  • GIVEN an RTMP ingress with a stream key
  • WHEN it is projected
  • THEN no document contains the stream key

Requirement: Raw events

Edition: CE and PRO. The events collection SHALL store one document per stored event, keyed by the LiveKit event id, with the type, timestamp, lifted entity references, node, a synthetic flag and a payload without secrets. The set of stored types SHALL be configurable with a default that excludes chatty and sensitive types.

Scenario: Fields of a raw event

  • WHEN a raw event is read
  • THEN it has _id (LiveKit event id), type, ts, roomSid, roomName, participantSid, identity, trackSid, egressId, ingressId, ingressSessionId (those that apply), nodeId, synthetic and payload

Scenario: Default stored types

  • GIVEN default configuration
  • WHEN API_CALL, TRACK_PUBLISHED_UPDATE, TRACK_MAX_SUBSCRIBED_VIDEO_QUALITY, TRACK_SUBSCRIBE_REQUESTED and two identical consecutive INGRESS_UPDATED events are received
  • THEN no API_CALL, TRACK_PUBLISHED_UPDATE, TRACK_MAX_SUBSCRIBED_VIDEO_QUALITY or TRACK_SUBSCRIBE_REQUESTED document is stored and only the first INGRESS_UPDATED is stored

Scenario: Room timeline

  • GIVEN events of a room within the events retention window
  • WHEN events is queried by roomSid and ts
  • THEN every stored event of the room is returned in time order with its lifted references, and none of them is an API_CALL

Scenario: Closure by the reconciler

  • GIVEN an active participant on a dead node
  • WHEN the reconciler closes it
  • THEN the document has status: "left" (or "aborted" when it never became active), closedBy: "reconciler", disconnectReason: "RECONCILED" and leftAt equal to the latest of lastStatsAt and lastEventAt

Scenario: Synthetic close event

  • GIVEN the reconciler closes a stale participant
  • WHEN it records the closure
  • THEN an events document with type: "PARTICIPANT_LEFT", synthetic: true and an _id prefixed AE_SYN_ exists

Requirement: Track statistics time series

Edition: CE and PRO. track_stats SHALL hold one document per 30-second flush per participant and direction (participant mode) or per track and direction (track mode), with normalized units, and signal/data pseudo-tracks SHALL only add to the participant byte totals, without track_stats documents.

Scenario: Fields of a stats document

  • WHEN a stats document is read
  • THEN it has ts, meta { roomSid, participantSid, trackSid (track mode only), kind (up|down), nodeId }, score, minScore, medianScore, bytes, packets, packetsLost, rttMs, jitterMs, nacks, plis, firs, frames, layers[] { layer, bytes, packets, frames }, mimeType (track mode only)

Scenario: Participant mode merges tracks

  • GIVEN participant mode and one flush with camera and microphone upstream stats of the same participant
  • WHEN the flush is stored
  • THEN one upstream document exists for the participant with bytes equal to the sum and score equal to the mean of the two tracks

Scenario: Fallback without time-series support

  • GIVEN a MongoDB-compatible server that rejects time-series collections
  • WHEN the writer initializes
  • THEN track_stats is created as a plain collection with the same document shape, the same secondary indexes and a TTL index on ts

Requirement: Rollups

Edition: CE and PRO. analytics_5min, analytics_hourly and analytics_daily SHALL hold one document per scope (global and node:<nodeId>) and bucket with pre-aggregated counters, so that long ranges never read entity or stats documents. Map keys SHALL be enum names or a fixed vocabulary, never user input. Every participant counter SHALL count STANDARD sessions only, except participants.byKind and the 5-minute participants.connected (participants with stats), which count every kind.

Scenario: Fields of the rollup documents

  • WHEN hourly or daily rollup documents are read
  • THEN they have counters for rooms (created, ended, by end reason, durationMsSum, participantsSum), participants (joined, active, aborted, left, by kind, SDK, OS, browser, connection type, disconnect reason, connect time, minutes), tracks, egress (started, by type, by end status, duration), ingress sessions (started, ended, started by input type, duration), bytes, quality (score sum/count, poor, lost), API calls and API errors (total and by method), and daily documents also uniqueIdentities (set by the server's reconciler once the UTC day has closed); 5-minute documents have bytes, quality, participants.connected (distinct participants with stats), the PARTICIPANT_ACTIVE count and the lifecycle counters (rooms created/ended, participants joined/left/aborted, egress started/ended)

Scenario: Five-minute bandwidth

  • GIVEN stats flushed between 10:00 and 10:05 UTC
  • WHEN analytics_5min is read for global:…T10:00:00Z
  • THEN bytes.up and bytes.down equal the sum of the flushed upstream and downstream bytes and quality.scoreCount equals the number of samples

Scenario: Hourly counters

  • GIVEN five rooms created and two ended between 10:00 and 11:00 UTC on one node
  • WHEN the events are processed
  • THEN analytics_hourly has global:2026-…T10:00:00Z and node:<id>:2026-…T10:00:00Z with rooms.created: 5 and rooms.ended: 2

Scenario: Replayed batch does not double count

  • GIVEN a batch whose raw events were already inserted
  • WHEN the batch is processed again
  • THEN rollup counters are unchanged

Scenario: Services in rollups

  • GIVEN between 10:00 and 11:00 UTC three STANDARD, one EGRESS and one AGENT participant joined, and the AGENT left after 10 minutes
  • WHEN the hourly rollup is read
  • THEN participants.joined is 3, participants.left is 0, participants.minutes counts only the STANDARD sessions and participants.byKind is { STANDARD: 3, EGRESS: 1, AGENT: 1 }

Scenario: Ingress sessions by input type

  • GIVEN two RTMP and one WHIP ingress sessions started between 10:00 and 11:00 UTC
  • WHEN the hourly rollup is read
  • THEN it has ingress.sessionsStarted: 3 and ingress.byInputType: { rtmp: 2, whip: 1 }

Requirement: Nodes

Edition: CE and PRO. The nodes collection SHALL hold one document per server node with firstSeenAt, lastSeenAt, serverVersion, region, batchIntervalMs (int32 milliseconds, at least 1 000: the node's configured batch interval) and mediaStats, updated by every writer batch, and SHALL expire documents 30 days after their last heartbeat so decommissioned nodes disappear. A document without batchIntervalMs SHALL be read as a 10 s interval, and one without mediaStats as true.

Scenario: Node heartbeat

  • GIVEN a node writing batches every 10 s
  • WHEN nodes is read
  • THEN its document has lastSeenAt within the last batch interval and batchIntervalMs: 10000

Scenario: Node document without the interval

  • GIVEN a nodes document without batchIntervalMs
  • WHEN the dashboard derives that node's freshness windows
  • THEN it uses a 10 000 ms interval

Scenario: Interval below the minimum

  • GIVEN the test configuration
  • WHEN a nodes document with batchIntervalMs: 500 is written
  • THEN the write is rejected by the validator

Scenario: Node document without media statistics

  • GIVEN a nodes document without mediaStats
  • WHEN the dashboard reads it
  • THEN it treats the node as reporting media statistics

Requirement: Retention

Edition: CE and PRO. Retention SHALL be configurable per class with defaults events 7 d, stats 7 d, tracks (tracks and subscriptions) 7 d, entities (rooms, participants, egress, ingress) 90 d, 5-minute rollups 7 d, hourly rollups 400 d, daily rollups unlimited unless configured. A closed entity document SHALL carry expiresAt = its end time plus its class retention, computed when written, and an open one expiresAt: null; events, track_stats and the rollups expire by TTL indexes.

Scenario: TTL indexes

  • GIVEN a writer that finished initialization with the default retention
  • WHEN the TTL indexes are listed
  • THEN events expires by ts after retention.events, track_stats by ts after retention.stats, analytics_5min by bucket after retention.rollups_5min, analytics_hourly by bucket after retention.rollups_hourly, every entity collection by expiresAt with expireAfterSeconds: 0, nodes by lastSeenAt after 30 days, and analytics_daily has no TTL index

Scenario: Daily rollups with a retention

  • GIVEN a writer that finished initialization with retention.rollups_daily: 1825d
  • WHEN the TTL indexes of analytics_daily are listed
  • THEN it expires by bucket after 1 825 days

Scenario: Expiry of closed entities

  • WHEN closed entity documents are read
  • THEN expiresAt is endedAt + retention.entities for rooms, leftAt + retention.entities for participants, endedAt (else updatedAt) + retention.entities for egresses in a terminal status, endedAt (else startedAt) + retention.entities for ended ingress sessions, unpublishedAt + retention.tracks for tracks, unsubscribedAt + retention.tracks for subscriptions and the failure time + retention.tracks for failed subscriptions

Scenario: Ingress definition

  • GIVEN a reusable ingress
  • WHEN its document is read before and after its INGRESS_DELETED
  • THEN expiresAt is null before, whatever its sessions, and deletedAt + retention.entities after

Scenario: Entities retention change

  • GIVEN a room closed while retention.entities was 90d
  • WHEN the writer restarts with retention.entities: 30d
  • THEN that room keeps its expiresAt and rooms closed afterwards get endedAt + 30 days

Scenario: Active entity never expires

  • GIVEN a room active for longer than the entities retention
  • WHEN the TTL monitor runs
  • THEN the room document exists

Scenario: Retention change applies to existing data

  • GIVEN a deployment that changes retention.events from 7 d to 30 d
  • WHEN the writer restarts
  • THEN the TTL index of events has the new expireAfterSeconds and schema_meta (_id: "retention") reflects the configured values

Requirement: Secrets and personal data

Edition: CE and PRO. No document SHALL contain turn_password, storage credentials, stream keys, signing keys, tokens or SIP credentials. Client IP addresses SHALL be stored only when anonymize_ip is false, and participant metadata, attributes, room metadata and ingress participant metadata SHALL be stored only when store_metadata is enabled.

Scenario: Anonymized IP

  • GIVEN the default configuration
  • WHEN a PARTICIPANT_JOINED with client_info.address is projected
  • THEN client.address is absent from the participant document and from the raw event payload

Scenario: IP addresses stored on request

  • GIVEN anonymize_ip: false
  • WHEN a PARTICIPANT_JOINED with client_info.address is projected
  • THEN the participant document has client.address

Scenario: Metadata disabled by default

  • GIVEN default configuration
  • WHEN a room with metadata and a participant with attributes are projected
  • THEN neither config.metadata nor metadata/attributes are stored

Requirement: Index-backed queries

Edition: CE and PRO. The writer SHALL create the indexes of the shared contract (dashboard-ce/typings/contract/indexes.ts) at initialization, and every list, detail and time-series query of the dashboard API SHALL use an index (no collection scan) on collections with more than a configurable threshold of documents.

Scenario: Explain plans in tests

  • GIVEN a database generated by the data generator at scenario M
  • WHEN each API query is explained
  • THEN no winning plan contains a COLLSCAN stage and totalDocsExamined is bounded by the page size plus the filtered range

Requirement: Units and time

Edition: CE and PRO. All timestamps SHALL be stored as UTC Date, durations as integer milliseconds and sizes as integer bytes; the writer SHALL convert the protocol's seconds, milliseconds, nanoseconds and microseconds accordingly. Bucketing by calendar unit SHALL be done at read time with the caller's timezone.

Scenario: Egress nanoseconds normalized

  • GIVEN an EgressInfo with started_at and ended_at in nanoseconds
  • WHEN projected
  • THEN startedAt and endedAt are Date values and durationMs is their difference in milliseconds

Requirement: Document validators

Edition: CE and PRO. Each collection SHALL have a JSON Schema validator derived from the shared contract, applied with validationAction: "warn" in production and "error" in tests, so a writer defect is visible in logs without losing data.

Scenario: Invalid document in tests

  • GIVEN the test configuration
  • WHEN a room document with createdAt as a string is inserted
  • THEN the insert is rejected by the validator

Requirement: Egress room-name search index

Edition: CE and PRO. egresses SHALL have the index roomNameLower_startedAt { roomNameLower: 1, startedAt: -1 }, partial on roomNameLower existing, which backs the case-insensitive prefix search on the egress room name with the range on startedAt. An egress document without roomNameLower is not matched by that search.

Scenario: Index of an initialized database

  • GIVEN a writer that finished initialization
  • WHEN the indexes of egresses are listed
  • THEN roomNameLower_startedAt exists with the key { roomNameLower: 1, startedAt: -1 } and the partial filter { roomNameLower: { $exists: true } }

Scenario: Document without the search field

  • GIVEN an egress document of room demo-room without roomNameLower
  • WHEN the egress list is searched by the room name demo
  • THEN that egress is not returned, and egresses of the same room that carry roomNameLower are

Requirement: Ingress last-session index

Edition: CE and PRO. ingresses SHALL have the index lastSessionAt_id { lastSessionAt: -1, _id: -1 }, partial on lastSessionAt existing, which backs the selection of the ingresses whose last session started in a time range.

Scenario: Index of an initialized database

  • GIVEN a writer that finished initialization
  • WHEN the indexes of ingresses are listed
  • THEN lastSessionAt_id exists with the key { lastSessionAt: -1, _id: -1 } and the partial filter { lastSessionAt: { $exists: true } }

Requirement: Generated data follows the writer's shape

Edition: CE and PRO. The data generator SHALL write documents in the writer's shape: event payloads as the writer's protojson (snake_case names nested under room, participant, track, egress and ingress, 64-bit integers as 64-bit numbers, as in the contract event fixtures), ingress nameLower and egress roomNameLower as the lower-cased name and roomName, and node heartbeats with batchIntervalMs 10 000.

Scenario: Generated event payload

  • GIVEN a database generated by the data generator
  • WHEN a generated PARTICIPANT_JOINED event is read
  • THEN its payload has room.sid, participant.sid, participant.identity and client_info.sdk, has no camelCase key such as clientInfo, and satisfies the payload interfaces of the typings

Scenario: Lower-cased search fields

  • GIVEN a generated ingress named Studio-A and a generated egress of room Demo-Room
  • WHEN their documents are read
  • THEN the ingress has nameLower: "studio-a" and the egress has roomNameLower: "demo-room"

Scenario: Generated node heartbeats

  • GIVEN a database generated by the data generator, kept live by its keep-alive
  • WHEN nodes is read
  • THEN every document has batchIntervalMs: 10000