Skip to content

Conversation

@ggazzo
Copy link
Member

@ggazzo ggazzo commented Sep 16, 2025

Summary by CodeRabbit

  • Refactor
    • Introduced a branded EventID type/schema; public APIs and internal types now use EventID instead of plain strings for event identifiers across packages.
  • Bug Fixes
    • Tombstone construction now includes only valid auth event IDs (no empty entries).
  • Tests
    • Updated test fixtures/typings to use EventID.
  • Chores
    • Aligned service/repository/controller signatures, emitted schemas, and payload typings with EventID; added casts at HTTP boundaries where needed.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 16, 2025

Walkthrough

Type-only migration: most event identifier types changed from plain string to branded EventID across core, federation-sdk, room, and homeserver packages; a few constructors now filter out undefined auth-event IDs before emitting auth_events. No runtime control-flow changes.

Changes

Cohort / File(s) Summary
Core event base & utils
packages/core/src/events/eventBase.ts, packages/core/src/utils/generateId.ts, packages/core/src/types.ts, packages/core/src/models/event.model.ts
Switched event identifier types to EventID / EventID[] (auth_events, prev_events, redacts, SignedEvent.event_id, PersistentEventBase._id); generateId now returns EventID.
Core event factories & types
packages/core/src/events/m.reaction.ts, packages/core/src/events/m.room.*.ts (guest_access, history_visibility, join_rules, member, message, name, power_levels, redaction, tombstone)
Typings and factory signatures updated to EventID/EventID[] for relates_to.event_id, auth_events, prev_events, redacts; tombstone builder filters undefined auth IDs.
Federation SDK — public types & schemas
packages/federation-sdk/src/index.ts, packages/federation-sdk/src/utils/event-schemas.ts
Event schema and exported signatures switched to use eventIdSchema / EventID for event_id, m.relates_to.event_id, redacts, etc.
Federation SDK — repositories
packages/federation-sdk/src/repositories/event.repository.ts, packages/federation-sdk/src/repositories/event-staging.repository.ts, packages/federation-sdk/src/repositories/room.repository.ts, packages/federation-sdk/src/repositories/state.repository.ts
Repository API signatures and stored types changed to EventID/EventID[]; Room.tombstone_event_id and StateStore.delta.eventId migrated to EventID.
Federation SDK — services
packages/federation-sdk/src/services/*.ts (event*, event-fetcher, message, missing-event, profiles, room, send-join, staging-area, state)
Public/internal method signatures updated to accept/return EventID/EventID[]; internal maps and state-building/promises aligned to EventID; minor casting at call sites.
Homeserver controllers
packages/homeserver/src/controllers/federation/*.ts, packages/homeserver/src/controllers/internal/*.ts
Incoming route params/body values cast to EventID/EventID[] when calling services; no behavior changes.
Room types, manager & state resolution
packages/room/src/types/_common.ts, packages/room/src/types/v3-11.ts, packages/room/src/manager/*.ts, packages/room/src/state_resolution/definitions/*
Introduced eventIdSchema and branded EventID (zod); PersistentEventBase.eventId and PersistentEventV3.eventId return EventID; EventStore.getEvents accepts EventID[]; test/types adjusted.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Controller as Homeserver Controller
  participant Service as Event/Room Service
  participant Repo as Repository / Store

  Controller->>Service: call API (params cast to EventID / EventID[])
  Service->>Repo: query/persist using EventID or EventID[]
  Note over Controller,Repo: All event-id parameters are EventID (type-only)
Loading
sequenceDiagram
  autonumber
  participant RoomSvc as RoomService
  participant AuthMap as AuthEvents Map
  participant Builder as Tombstone Builder

  RoomSvc->>AuthMap: collect auth candidate IDs (may be undefined)
  AuthMap-->>RoomSvc: map with optional IDs
  RoomSvc->>Builder: filter defined IDs -> auth_events: EventID[]
  Builder-->>RoomSvc: tombstone event payload (auth_events typed EventID[])
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • sampaiodiego

Poem

A hop of types, a tidy little shift,
Strings get branded, EventIDs lift.
Auth chains trimmed, no empties remain,
Reactions and redacts all use the same name.
I twitch my nose — compile passes again! 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "refactor: replace string event IDs with EventID" accurately and concisely summarizes the PR's primary change — converting string-typed event identifiers to the EventID type across the codebase — and uses a clear conventional prefix for intent; it directly matches the many type/signature updates shown in the changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/branded_event_ids

Comment @coderabbitai help to get the list of available commands and usage tips.

@ggazzo ggazzo changed the title fix: streamline join event creation and enhance error messaging for e… refactor: replace string event IDs with EventID Sep 16, 2025
@codecov-commenter
Copy link

codecov-commenter commented Sep 16, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.14%. Comparing base (0e73874) to head (a01f95f).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #200      +/-   ##
==========================================
+ Coverage   81.12%   81.14%   +0.01%     
==========================================
  Files          62       63       +1     
  Lines        4662     4666       +4     
==========================================
+ Hits         3782     3786       +4     
  Misses        880      880              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ggazzo ggazzo force-pushed the chore/branded_event_ids branch from 54dc4cb to 385b58a Compare September 16, 2025 12:50
@ggazzo ggazzo force-pushed the chore/branded_event_ids branch from 613a78c to b50354c Compare September 16, 2025 13:03
@ggazzo
Copy link
Member Author

ggazzo commented Sep 16, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 16, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
packages/federation-sdk/src/repositories/room.repository.ts (2)

54-60: Bug: projection path is wrong; getRoomVersion always returns null.

The version lives under room.version; projecting version omits the room object.

- const room = await this.collection.findOne(
-   { _id: roomId },
-   { projection: { version: 1 } },
- );
+ const room = await this.collection.findOne(
+   { _id: roomId },
+   { projection: { 'room.version': 1 } },
+ );

62-68: Bug: filter uses room_id instead of _id; update will never match.

- await this.collection.updateOne(
-   { room_id: roomId },
-   { $set: { name: name } },
-   { upsert: false },
- );
+ await this.collection.updateOne(
+   { _id: roomId },
+   { $set: { 'room.name': name } },
+   { upsert: false },
+ );
packages/room/src/state_resolution/definitions/algorithm/v2.spec.ts (2)

57-67: Branding mismatch: assigning a plain string to EventID.

_event_id is branded; constructing it with a string breaks the brand in strict mode. Cast or brand via a helper.

Apply:

-	_event_id: EventID;
+	_event_id: EventID;

@@
-		this._event_id = `${id}:example.com`;
+		this._event_id = `${id}:example.com` as EventID;

15-19: MockEventStore signature must match updated EventStore (EventID[]).

Interface now requires EventID[]. Update the mock to avoid type mismatch.

Apply:

-	async getEvents(eventIds: string[]): Promise<PersistentEventBase[]> {
+	async getEvents(eventIds: EventID[]): Promise<PersistentEventBase[]> {
 		return this.events.filter((e) => eventIds.includes(e.eventId));
 	}
packages/room/src/types/v3-11.ts (1)

134-137: Migrate remaining event_id strings to eventIdSchema.

Replace the z.string() event_id fields in packages/room/src/types/v3-11.ts at lines 134–136, 387–390, 397–399, 520–522 with the branded schema.

@@
-			event_id: z
-				.string()
-				.describe('The event ID of the last known event in the old room.'),
+			event_id: eventIdSchema.describe(
+				'The event ID of the last known event in the old room.',
+			),
@@
-			event_id: z
-				.string()
-				.optional()
-				.describe('The ID of the event that is being related to.'),
+			event_id: eventIdSchema
+				.optional()
+				.describe('The ID of the event that is being related to.'),
@@
-					event_id: z
-						.string()
-						.describe('The ID of the latest event in the thread for fallback'),
+					event_id: eventIdSchema.describe(
+						'The ID of the latest event in the thread for fallback',
+					),
@@
-		event_id: z
-			.string()
-			.describe('The ID of the event that is being annotated.'),
+		event_id: eventIdSchema.describe('The ID of the event that is being annotated.'),
packages/federation-sdk/src/repositories/event.repository.ts (2)

296-301: Filter by _id (use EventID[]) — query uses non-existent eventId

This query filters on a non-existent field (eventId) and will return no results; change to _id and use EventID[]; update callers and add a quick test.

File: packages/federation-sdk/src/repositories/event.repository.ts Lines: 296-301

-  findFromNonPublicRooms(eventIds: string[]): FindCursor<EventStore> {
-    return this.collection.find({
-      eventId: { $in: eventIds },
+  findFromNonPublicRooms(eventIds: EventID[]): FindCursor<EventStore> {
+    return this.collection.find({
+      _id: { $in: eventIds },
       'event.content.join_rule': { $ne: 'public' },
     });
   }

Update callers to pass EventID[] and add a quick test.


365-385: Wrong field and type for exclusion filter — fix to use _id and EventID[]; add test

Stored events use _id for the event ID; the current query filters on event_id and accepts string[], so exclusions never match. Update the parameter type and filter.

File: packages/federation-sdk/src/repositories/event.repository.ts (findEventsByRoomAndDepth, ~lines 365–385)

-  findEventsByRoomAndDepth(
+  findEventsByRoomAndDepth(
     roomId: string,
     minDepth: number,
     maxDepth: number,
-    excludeEventIds: string[],
+    excludeEventIds: EventID[],
     limit: number,
   ): FindCursor<EventStore<Pdu>> {
     return this.collection
       .find(
         {
           'event.room_id': roomId,
           'event.depth': { $gte: minDepth, $lte: maxDepth },
-          event_id: { $nin: excludeEventIds },
+          _id: { $nin: excludeEventIds },
         },
         {
           projection: { event: 1 },
         },
       )
       .sort({ 'event.depth': 1 })
       .limit(limit);
   }

Add a unit/integration test asserting excluded IDs are not returned.

packages/federation-sdk/src/services/event.service.ts (1)

472-498: Fix findEventsByRoomAndDepth to accept EventID[] and filter on _id
findEventsByRoomAndDepth currently types excludeEventIds as string[] and queries event_id: { $nin: ... } (packages/federation-sdk/src/repositories/event.repository.ts, ~line 365). The service passes EventID[] and repository events are stored under _id. Change the method signature to excludeEventIds: EventID[] and update the query to _id: { $nin: excludeEventIds }. After applying the change, re-run missing-events tests.

🧹 Nitpick comments (32)
packages/room/src/state_resolution/definitions/algorithm/v2.ts (2)

107-108: EventID[] migration here is good; tighten the wrapper’s method signature for safety.

eventIdsToFind as EventID[] aligns with the store API. Consider typing the wrapper to EventStore and the param to readonly EventID[] to prevent accidental string arrays.

- const wrappedStore: typeof store = {
-   async getEvents(eventIds) {
+ const wrappedStore: EventStore = {
+   async getEvents(eventIds: readonly EventID[]) {

175-184: Batch fetch conflicted power events instead of N sequential awaits.

Current loop does 1 RPC per ID. Collect and fetch in one call to reduce I/O.

- for (const eventid of fullConflictedSet) {
-   const [event] = await wrappedStore.getEvents([eventid]);
-   if (!event) {
-     console.warn('event not found in eventMap', eventid);
-     continue;
-   }
-   if (isPowerEvent(event)) {
-     powerEvents.set(event.eventId, event);
-   }
- }
+ {
+   const ids = Array.from(fullConflictedSet);
+   const events = await wrappedStore.getEvents(ids);
+   for (const event of events) {
+     if (!event) continue;
+     if (isPowerEvent(event)) {
+       powerEvents.set(event.eventId, event);
+     }
+   }
+ }
packages/federation-sdk/src/repositories/room.repository.ts (1)

15-16: Align types: tombstone_event_id is EventID; accept EventID in markRoomAsDeleted.

Avoid passing plain string to a branded EventID field.

- async markRoomAsDeleted(
-   roomId: string,
-   tombstoneEventId: string,
- ): Promise<void> {
+ async markRoomAsDeleted(
+   roomId: string,
+   tombstoneEventId: EventID,
+ ): Promise<void> {
   await this.collection.updateOne(
     { _id: roomId },
     {
       $set: {
         'room.deleted': true,
         'room.tombstone_event_id': tombstoneEventId,
       },
     },
   );
 }

Also applies to: 73-86

packages/federation-sdk/src/repositories/state.repository.ts (1)

37-41: getLatestStateMapping returns the oldest due to ascending sort.

Use -1 to fetch the latest.

- return this.collection.findOne({ roomId }, { sort: { createdAt: 1 } });
+ return this.collection.findOne({ roomId }, { sort: { createdAt: -1 } });
packages/core/src/events/m.room.guest_access.ts (1)

1-1: Use package alias and type-only import for EventID; param type changes LGTM.

Avoid deep path; keep consistency with other files.

-import { EventID } from 'packages/room/dist';
+import type { EventID } from '@hs/room';

Also applies to: 28-30

packages/federation-sdk/src/services/missing-event.service.ts (1)

3-3: Use package alias and type-only import; name the logger after the service.

Small hygiene tweaks.

-import { EventID } from 'packages/room/dist';
+import type { EventID } from '@hs/room';
-private readonly logger = createLogger('EventService');
+private readonly logger = createLogger('MissingEventService');

Also applies to: 17-17

packages/homeserver/src/controllers/internal/room.controller.ts (1)

4-8: Mark EventID as type-only; parse event_id instead of casting.

This preserves the brand and avoids leaking values imports.

-import {
-  EventID,
-  type PduCreateEventContent,
-  PersistentEventFactory,
-} from '@hs/room';
+import {
+  type EventID,
+  type PduCreateEventContent,
+  PersistentEventFactory,
+} from '@hs/room';
- const room = await stateService.findStateAtEvent(eventId as EventID);
+ import { eventIdSchema } from '@hs/room';
+ const room = await stateService.findStateAtEvent(eventIdSchema.parse(eventId));

Also applies to: 192-193

packages/homeserver/src/controllers/internal/message.controller.ts (1)

119-119: LGTM on EventID casts; make the other call sites consistent.

unsetReaction and redactMessage now pass EventID. Do the same for updateMessage and sendReaction to avoid mixed typing.

Suggested updates elsewhere in this file:

// in updateMessage handler
params.messageId as EventID

// in sendReaction handler
params.messageId as EventID

Please confirm MessageService method signatures now expect EventID. If not, align them or narrow here.

Also applies to: 158-158

packages/core/src/models/event.model.ts (1)

1-1: Great: persisted _id is EventID. Now align adjacent ID fields.

To keep branding consistent and prevent accidental mixing with plain strings, consider updating nearby types:

  • missing_dependencies?: EventID[]
  • EventStore.nextEventId: EventID
  • FetchedEvents.events[].eventId: EventID
  • FetchedEvents.missingEventIds: EventID[]

Example (outside selected lines):

interface PersistentEventBase<E = Pdu> {
  _id: EventID;
  // ...
  missing_dependencies?: EventID[];
}

export interface EventStore<E = Pdu> extends PersistentEventBase<E> {
  stateId: string;
  nextEventId: EventID;
}

export interface FetchedEvents {
  events: { eventId: EventID; event: CoreEventBase }[];
  missingEventIds: EventID[];
}

Also applies to: 7-7

packages/federation-sdk/src/services/send-join.service.ts (1)

3-3: Good: public API now takes EventID; tighten local typing and avoid variable shadowing.

  • Type authChain as EventID[] to preserve branding end‑to‑end.
  • Avoid reusing the identifier event inside the for...of loop (shadows the method param).

Outside the selected lines:

const authChain: EventID[] = [];

for (const ev of state.values()) {
  const authEvents = await getAuthChain(ev, stateService._getStore(roomVersion));
  authChain.push(...authEvents);
}

Also applies to: 24-24

packages/federation-sdk/src/services/staging-area.service.ts (1)

6-6: Remove redundant as EventID casts in emitter payloads.

eventId is already EventID (via EventStagingStore._id). Casting is unnecessary noise.

Apply:

- event_id: eventId as EventID,
+ event_id: eventId,

Repeat for the redaction and membership emitters. The type‑only import on Line 6 is correct.

Also applies to: 166-167, 182-183, 195-196

packages/room/src/manager/v3.ts (1)

19-21: Avoid casts by typing raw_event.prev_events as EventID[] at the source.

The cast works, but updating the PDU type would remove the need for as EventID[].

packages/federation-sdk/src/services/room.service.ts (2)

1040-1049: Auth event map now uses actual IDs; consider enforcing required keys.

Filtering undefined later is good, but tombstone should require create, power_levels, and member. Fail fast if any are missing.

Apply:

 		const authEventsMap: TombstoneAuthEvents = {
 			'm.room.create': authEvents.find(
 				(event) => event.event.type === 'm.room.create',
 			)?._id,
 			'm.room.power_levels': authEvents.find(
 				(event) => event.event.type === 'm.room.power_levels',
 			)?._id,
 			'm.room.member': authEvents.find(
 				(event) => event.event.type === 'm.room.member',
 			)?._id,
 		};
+		const missing = Object.entries(authEventsMap)
+			.filter(([, id]) => !id)
+			.map(([t]) => t);
+		if (missing.length) {
+			throw new HttpException(
+				`Missing auth events for tombstone: ${missing.join(', ')}`,
+				HttpStatus.FORBIDDEN,
+			);
+		}

1052-1055: Minor: tighten filtering with a type guard.

Keeps types precise without an assertion.

Apply:

-		const authEventsArray = Object.values(authEventsMap).filter(
-			(event) => event !== undefined,
-		) as EventID[];
+		const authEventsArray = Object.values(authEventsMap).filter(
+			(e): e is EventID => Boolean(e),
+		);
packages/core/src/utils/generateId.ts (2)

2-2: Mark EventID import as type‑only.

-import { EventID } from 'packages/room/dist';
+import type { EventID } from 'packages/room/dist';

7-15: Return type change to EventID looks good; casting is acceptable.

Optional: centralize branding to reduce repeated as EventID casts (e.g., a tiny asEventID(s: string): EventID helper).

packages/core/src/events/m.room.tombstone.ts (2)

1-1: Type‑only import for EventID.

-import { EventID } from 'packages/room/dist';
+import type { EventID } from 'packages/room/dist';

41-45: isTruthy helper is duplicated across modules.

Consider a shared utility (e.g., utils/predicates.ts) to DRY.

packages/room/src/types/v3-11.ts (1)

556-561: Use eventIdSchema for top‑level redacts to finish the migration.

-	redacts: z
-		.string()
-		.describe(
-			'The ID of the event that this event redacts. This is an optional field.',
-		)
-		.optional(),
+	redacts: eventIdSchema
+		.describe('The ID of the event that this event redacts.')
+		.optional(),
packages/core/src/events/m.room.power_levels.ts (2)

1-1: Type‑only import for EventID.

-import { EventID } from 'packages/room/dist';
+import type { EventID } from 'packages/room/dist';

57-60: Make 'm.room.member' optional (matches TODO/tests) and avoid cast with a typed predicate.

-	auth_events: {
-		'm.room.create': EventID;
-		'm.room.power_levels': EventID;
-		'm.room.member': EventID; // TODO: Based on the tests, this is optional, based on the code, this is required check this
-	};
+	auth_events: {
+		'm.room.create': EventID;
+		'm.room.power_levels': EventID;
+		'm.room.member'?: EventID; // optional per tests
+	};
@@
-		auth_events: [
+		auth_events: [
 			auth_events['m.room.create'],
 			auth_events['m.room.power_levels'],
 			auth_events['m.room.member'],
-		].filter(Boolean) as EventID[],
+		].filter((e): e is EventID => Boolean(e)),

If any callers relied on 'm.room.member' being required, CI/type‑check will surface them. Please confirm no regressions.

packages/federation-sdk/src/services/message.service.ts (4)

75-76: Fix typos: “white” → “while”.

Tiny copy edit in error messages.

-`Room version not found for room ${roomId} white trying to send message`,
+`Room version not found for room ${roomId} while trying to send message`,

Also applies to: 117-120


379-413: Variable name “redactionEvent” is misleading for an edit event.

This block creates an m.room.message replacement, not a redaction. Rename for clarity.

-const redactionEvent = await this.stateService.buildEvent<'m.room.message'>(
+const replacementEvent = await this.stateService.buildEvent<'m.room.message'>(
 ...
-await this.stateService.persistTimelineEvent(redactionEvent);
-void this.federationService.sendEventToAllServersInRoom(redactionEvent);
-return redactionEvent.eventId;
+await this.stateService.persistTimelineEvent(replacementEvent);
+void this.federationService.sendEventToAllServersInRoom(replacementEvent);
+return replacementEvent.eventId;

339-353: Good: unsetReaction now brands the target ID. Also update return type.

Return should be EventID for consistency with the new branding.

-): Promise<string> {
+): Promise<EventID> {

417-453: Good: redactMessage now brands the target ID. Also update return type.

Return should be EventID as well.

-): Promise<string> {
+): Promise<EventID> {
packages/federation-sdk/src/repositories/event-staging.repository.ts (1)

3-3: Prefer type-only import for Pdu.

Avoids emitting a runtime import for a type.

-import { type EventID, Pdu } from '@hs/room';
+import { type EventID, type Pdu } from '@hs/room';
packages/federation-sdk/src/utils/event-schemas.ts (1)

13-18: Align prev_events/auth_events with EventID branding.

These still accept string/tuple. If you intend to enforce EventID, update schemas to brand event IDs too while preserving tuple support.

-prev_events: z
-  .array(z.string().or(z.tuple([z.string(), z.string()])))
-  .optional(),
-auth_events: z
-  .array(z.string().or(z.tuple([z.string(), z.string()])))
-  .optional(),
+prev_events: z
+  .array(eventIdSchema.or(z.tuple([eventIdSchema, z.string()])))
+  .optional(),
+auth_events: z
+  .array(eventIdSchema.or(z.tuple([eventIdSchema, z.string()])))
+  .optional(),

If inbound federation must accept legacy strings, keep the union but brand the event_id member as above.

packages/room/src/types/_common.ts (2)

4-8: Branding scaffold is solid; consider not exporting the brand symbol.

Exporting Brand/Branded is fine; exporting the unique symbol is usually unnecessary. Optional.

-export declare const __brand: unique symbol;
+declare const __brand: unique symbol;
+export { __brand as /* internal */ __brand };

Or simply keep it non-exported if not needed by consumers of Brand/Branded.


1-1: Minor consistency nit: prefer named import from zod.

Other files import { z }. Standardize if you like.

-import z from 'zod';
+import { z } from 'zod';
packages/core/src/events/m.room.redaction.ts (1)

1-1: Unify import path and make it type-only.

Use the public barrel and avoid value import.

-import { EventID } from 'packages/room/dist';
+import type { EventID } from '@hs/room';
packages/federation-sdk/src/services/state.service.ts (1)

778-779: Drop redundant cast to EventID[].

Pdu.auth_events should already be EventID[]. Remove the cast to keep types honest.

-        const authEventsReferencedInMessage = await store.getEvents(
-            event.event.auth_events as EventID[],
-        );
+        const authEventsReferencedInMessage = await store.getEvents(
+            event.event.auth_events,
+        );

If this fails TS, there’s a lingering string[] somewhere in Pdu; I can help locate and fix.

packages/federation-sdk/src/repositories/event.repository.ts (1)

205-210: Minor: redundant _id != '' filter.

new events are always non-empty ids; this predicate adds noise and no safety.

-      .find({
-        nextEventId: '',
-        'event.room_id': roomId,
-        _id: { $ne: '' as EventID },
-      })
+      .find({
+        nextEventId: '',
+        'event.room_id': roomId,
+      })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e73874 and b50354c.

📒 Files selected for processing (42)
  • packages/core/src/events/eventBase.ts (4 hunks)
  • packages/core/src/events/m.reaction.ts (5 hunks)
  • packages/core/src/events/m.room.guest_access.ts (2 hunks)
  • packages/core/src/events/m.room.history_visibility.ts (2 hunks)
  • packages/core/src/events/m.room.join_rules.ts (2 hunks)
  • packages/core/src/events/m.room.member.ts (3 hunks)
  • packages/core/src/events/m.room.message.ts (4 hunks)
  • packages/core/src/events/m.room.name.ts (3 hunks)
  • packages/core/src/events/m.room.power_levels.ts (3 hunks)
  • packages/core/src/events/m.room.redaction.ts (4 hunks)
  • packages/core/src/events/m.room.tombstone.ts (3 hunks)
  • packages/core/src/models/event.model.ts (1 hunks)
  • packages/core/src/types.ts (1 hunks)
  • packages/core/src/utils/generateId.ts (1 hunks)
  • packages/federation-sdk/src/index.ts (5 hunks)
  • packages/federation-sdk/src/repositories/event-staging.repository.ts (3 hunks)
  • packages/federation-sdk/src/repositories/event.repository.ts (8 hunks)
  • packages/federation-sdk/src/repositories/room.repository.ts (2 hunks)
  • packages/federation-sdk/src/repositories/state.repository.ts (1 hunks)
  • packages/federation-sdk/src/services/event-fetcher.service.ts (2 hunks)
  • packages/federation-sdk/src/services/event.service.ts (9 hunks)
  • packages/federation-sdk/src/services/message.service.ts (3 hunks)
  • packages/federation-sdk/src/services/missing-event.service.ts (1 hunks)
  • packages/federation-sdk/src/services/profiles.service.ts (2 hunks)
  • packages/federation-sdk/src/services/room.service.ts (3 hunks)
  • packages/federation-sdk/src/services/send-join.service.ts (2 hunks)
  • packages/federation-sdk/src/services/staging-area.service.ts (5 hunks)
  • packages/federation-sdk/src/services/state.service.ts (7 hunks)
  • packages/federation-sdk/src/utils/event-schemas.ts (4 hunks)
  • packages/homeserver/src/controllers/federation/profiles.controller.ts (2 hunks)
  • packages/homeserver/src/controllers/federation/send-join.controller.ts (2 hunks)
  • packages/homeserver/src/controllers/federation/state.controller.ts (3 hunks)
  • packages/homeserver/src/controllers/federation/transactions.controller.ts (2 hunks)
  • packages/homeserver/src/controllers/internal/message.controller.ts (3 hunks)
  • packages/homeserver/src/controllers/internal/room.controller.ts (2 hunks)
  • packages/room/src/manager/event-wrapper.ts (3 hunks)
  • packages/room/src/manager/v3.ts (3 hunks)
  • packages/room/src/state_resolution/definitions/algorithm/v2.spec.ts (2 hunks)
  • packages/room/src/state_resolution/definitions/algorithm/v2.ts (1 hunks)
  • packages/room/src/state_resolution/definitions/definitions.ts (1 hunks)
  • packages/room/src/types/_common.ts (1 hunks)
  • packages/room/src/types/v3-11.ts (4 hunks)
🔇 Additional comments (35)
packages/federation-sdk/src/repositories/state.repository.ts (1)

11-19: EventID branding in delta.eventId: LGTM.

Type-level change is consistent with upstream types and assignments.

Also applies to: 70-77

packages/core/src/types.ts (1)

2-10: SignedEvent.event_idEventID: LGTM.

Type-only import keeps runtime clean.

packages/homeserver/src/controllers/federation/send-join.controller.ts (1)

2-2: LGTM: type‑only import and EventID cast are consistent with the migration.

If SendJoinService later loosens its input (e.g., branded validators), this cast can be removed.

Also applies to: 23-23

packages/homeserver/src/controllers/federation/state.controller.ts (1)

22-22: Incorrect — event_id is required by DTO; cast is safe

GetStateQueryDto and GetStateIdsQueryDto declare event_id as a required t.String (packages/homeserver/src/dtos/federation/state.dto.ts and packages/homeserver/src/dtos/federation/state-ids.dto.ts) and eventService.getState / getStateIds expect a non-optional EventID — no change required.

Likely an incorrect or invalid review comment.

packages/room/src/state_resolution/definitions/algorithm/v2.spec.ts (1)

1-1: Type import alignment looks good.

Using a type‑only import for EventID/StateMapKey keeps the test runtime‑clean.

packages/room/src/manager/v3.ts (1)

31-33: EventID computation is correct and branded.

Escaped $ and urlSafe base64 match Matrix v3 spec; branding is explicit.

packages/room/src/manager/event-wrapper.ts (1)

120-120: Public API change: eventId now branded.

Good alignment with EventID across the codebase.

packages/federation-sdk/src/services/profiles.service.ts (1)

109-115: Type migration to EventID[] for missing events is consistent.

Signature aligns with EventService; pass‑through preserved.

packages/federation-sdk/src/services/room.service.ts (1)

1064-1064: LGTM on using the filtered auth_events array.

Prevents empty/undefined IDs leaking into the tombstone.

packages/core/src/events/m.room.name.ts (1)

20-23: EventID migration here is consistent and correct.

Also applies to: 56-56

packages/core/src/events/m.room.tombstone.ts (1)

24-27: Auth/prev_events typed to EventID (optionals filtered) — LGTM.

Also applies to: 35-35

packages/room/src/types/v3-11.ts (2)

2-2: Schema shifts to eventIdSchema are correct and consistent with the EventID migration.

Also applies to: 200-201, 534-535, 552-553


198-201: Spec alignment: clarify or remove content.redacts in redaction schema

Tests in the repo reference content.redacts, so removing it will affect consumers. Locations: packages/room/src/types/v3-11.ts — PduRoomRedactionContentSchema (lines 198–201); usages: packages/room/src/manager/event-wrapper.spec.ts (lines 267, 282, 287). Action: either add an inline comment in the schema documenting that content.redacts is intentionally present for compatibility, or remove content.redacts from the schema and update the referenced tests/consumers to use top‑level redacts.

packages/core/src/events/m.room.member.ts (2)

21-28: AuthEvents/EventID migration looks correct.

Keys and the dynamic member mapping now brand to EventID. Nice.


54-54: prev_events → EventID[] is aligned with the new EventBase typing.

LGTM.

packages/federation-sdk/src/repositories/event-staging.repository.ts (2)

16-21: create(eventId: EventID, …): upsert logic remains correct.

Branding the _id with EventID is aligned with the migration.


43-45: removeByEventId(eventId: EventID): aligned and clear.

LGTM.

packages/federation-sdk/src/utils/event-schemas.ts (4)

1-1: Importing eventIdSchema is the right move.

Enables branded validation where needed.


10-11: event_id now uses eventIdSchema.

Consistent with EventID branding. Good.


66-67: reaction: relates_to.event_id uses eventIdSchema.

Matches the spec and the new typing.


101-104: redaction.redacts uses eventIdSchema.

Good consistency.

packages/core/src/events/m.room.message.ts (4)

1-1: Type-only EventID import: good.

Consistent with the branding approach.


76-86: Relation event_id fields branded to EventID.

This tightens types for both m.annotation and m.replace.


94-98: Auth events now require EventID.

Correct and consistent with event base changes.


137-144: prev_events: EventID[] is correct.

Matches the updated EventBase expectations.

packages/room/src/types/_common.ts (1)

11-14: eventIdSchema and EventID type look good.

Runtime validation + branded type are aligned.

packages/federation-sdk/src/services/message.service.ts (1)

17-21: Migrate all event-id params/returns in message.service.ts to EventID — verification required

Ran the ripgrep check; it returned no output, so I could not confirm whether remaining methods still use string-typed event IDs. Re-run this to verify presence of string-typed event IDs:

rg -n --type=ts -C2 $'\b(eventId|eventIdToRedact|eventIdToReplace|eventToReplyTo|threadRootEventId|latestThreadEventId)\s*:\s*string\b' packages/federation-sdk/src/services/message.service.ts

If matches exist, convert these signatures to EventID/Promise (minimal deltas):

-  ): Promise<string> {
+  ): Promise<EventID> {

async sendReaction(
  roomId: string,
-  eventId: string,
+  eventId: EventID,
  emoji: string,
  senderUserId: string,
-): Promise<string> {
+): Promise<EventID> {

async sendReplyToMessage(
  roomId: string,
  rawMessage: string,
  formattedMessage: string,
-  eventToReplyTo: string,
+  eventToReplyTo: EventID,
  senderUserId: string,
): Promise<PersistentEventBase> {

async sendThreadMessage(
  roomId: string,
  rawMessage: string,
  formattedMessage: string,
  senderUserId: string,
-  threadRootEventId: string,
-  latestThreadEventId?: string,
+  threadRootEventId: EventID,
+  latestThreadEventId?: EventID,
): Promise<PersistentEventBase> {

async sendReplyToInsideThreadMessage(
  roomId: string,
  rawMessage: string,
  formattedMessage: string,
  senderUserId: string,
-  threadRootEventId: string,
-  eventToReplyTo: string,
+  threadRootEventId: EventID,
+  eventToReplyTo: EventID,
): Promise<PersistentEventBase> {

async updateMessage(
  roomId: string,
  rawMessage: string,
  formattedMessage: string,
  senderUserId: string,
-  eventIdToReplace: string,
-): Promise<string> {
+  eventIdToReplace: EventID,
+): Promise<EventID> {
packages/core/src/events/m.room.redaction.ts (1)

12-15: LGTM: EventID typed redaction fields.

Auth event IDs, content.redacts, and prev_events are correctly branded as EventID.

Also applies to: 28-28, 51-51, 54-54

packages/core/src/events/m.reaction.ts (1)

1-1: LGTM: Reaction event surfaces now use EventID.

Imports and all event_id fields consistently branded; no logic changes.

Also applies to: 11-11, 23-26, 37-37, 66-66, 71-71

packages/federation-sdk/src/services/state.service.ts (1)

119-121: LGTM: State service API/storage maps migrated to EventID.

Signatures and internal maps now carry EventID without behavior changes.

Also applies to: 256-258, 271-273, 348-351, 376-379

packages/federation-sdk/src/index.ts (1)

2-2: LGTM: Public Homeserver event signatures branded with EventID.

Consistent, non-breaking type-level changes.

Also applies to: 110-110, 119-119, 121-121, 136-136, 147-147, 154-154, 160-160, 164-164, 170-170

packages/federation-sdk/src/services/event.service.ts (2)

758-759: LGTM: Auth chain tracked as Set.

Matches EventID branding all the way to store.getEvents.


69-78: Bug: getEventById passes eventId to findByRoomIdAndType (uses room lookup instead of event ID).

Call findById(eventId), return null if not found, then return null if res.event.type !== type; otherwise return res. Add/adjust tests to cover both branches (with and without type). Location: packages/federation-sdk/src/services/event.service.ts lines 69–78

packages/federation-sdk/src/repositories/event.repository.ts (1)

268-274: LGTM: findByIds now typed to EventID[].

Consistent with the rest of the repository API.

packages/core/src/events/eventBase.ts (1)

5-6: LGTM: EventID migration on core event base.

Types updated correctly for auth_events/prev_events and redacts — confirmed that packages/core/src/events use EventID (e.g., packages/core/src/events/eventBase.ts lines 5–6) and no remaining string-typed auth_events/prev_events/redacts were found. No runtime behavior changes introduced.

@@ -1,3 +1,4 @@
import { EventID } from 'packages/room/dist';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrong import path for EventID; use package alias.

Using 'packages/room/dist' will break outside the monorepo build. Import from '@hs/room' as a type.

Apply:

-import { EventID } from 'packages/room/dist';
+import type { EventID } from '@hs/room';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { EventID } from 'packages/room/dist';
import type { EventID } from '@hs/room';
🤖 Prompt for AI Agents
In packages/core/src/events/m.room.history_visibility.ts around line 1, the file
currently imports EventID from the local path 'packages/room/dist' which will
break consumers outside the monorepo; change the import to use the package alias
and import EventID as a type from '@hs/room' (e.g. replace the existing import
with a type-only import from '@hs/room') so builds and external consumers
resolve the symbol correctly.

@@ -1,3 +1,4 @@
import { EventID } from 'packages/room/dist';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🧩 Analysis chain

Fix EventID import path; prefer type‑only import.

Use the same source as the rest of the repo and avoid pulling runtime code for a type.

Apply:

-import { EventID } from 'packages/room/dist';
+import type { EventID } from '@hs/room';

The EventID[] parameter changes on Lines 37–38 look correct and align with the project‑wide migration.

Also applies to: 37-38


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== show target file (first 200 lines) ==="
if [ -f packages/core/src/events/m.room.join_rules.ts ]; then
  sed -n '1,240p' packages/core/src/events/m.room.join_rules.ts || true
else
  echo "MISSING: packages/core/src/events/m.room.join_rules.ts"
fi

echo
echo "=== show lines 1-120 with numbers for quick reference ==="
if [ -f packages/core/src/events/m.room.join_rules.ts ]; then
  nl -ba -w3 -s': ' packages/core/src/events/m.room.join_rules.ts | sed -n '1,120p' || true
fi

echo
echo "=== search repo for EventID symbol usages (context) ==="
if command -v rg >/dev/null 2>&1; then
  rg -n --hidden -S '\bEventID\b' || true
else
  grep -RIn --line-number '\bEventID\b' . || true
fi

echo
echo "=== search for imports that pull from packages/room/dist ==="
if command -v rg >/dev/null 2>&1; then
  rg -n --hidden -S "packages/room/dist" || true
else
  grep -RIn --line-number "packages/room/dist" . || true
fi

echo
echo "=== search for @hs/room occurrences ==="
if command -v rg >/dev/null 2>&1; then
  rg -n --hidden -S "@hs/room" || true
else
  grep -RIn --line-number "@hs/room" . || true
fi

echo
echo "=== packages/room package.json (if present) ==="
if [ -f packages/room/package.json ]; then
  sed -n '1,240p' packages/room/package.json || true
else
  echo "MISSING: packages/room/package.json"
fi

echo
echo "=== list top files under packages/room ==="
if [ -d packages/room ]; then
  find packages/room -maxdepth 3 -type f | sed -n '1,200p' || true
else
  echo "MISSING: packages/room directory"
fi

echo
echo "=== show root package.json and tsconfig.json (first 200 lines) ==="
if [ -f package.json ]; then
  sed -n '1,240p' package.json || true
else
  echo "MISSING: package.json"
fi

echo
if [ -f tsconfig.json ]; then
  sed -n '1,240p' tsconfig.json || true
else
  echo "MISSING: tsconfig.json"
fi

Length of output: 29169


Fix EventID import — use a type-only import from @hs/room

Apply:

-import { EventID } from 'packages/room/dist';
+import type { EventID } from '@hs/room';

auth_events / prev_events (lines 37–38) are already correctly typed as EventID[].

Also update other occurrences that import from 'packages/room/dist' (e.g. packages/core/src/utils/generateId.ts, packages/core/src/events/*.ts, packages/federation-sdk/src/services/missing-event.service.ts).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { EventID } from 'packages/room/dist';
import type { EventID } from '@hs/room';

@@ -1,3 +1,4 @@
import { EventID } from 'packages/room/dist';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix import path and make it type‑only.

Use the package alias consistently and avoid a runtime import.

-import { EventID } from 'packages/room/dist';
+import type { EventID } from '@hs/room';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { EventID } from 'packages/room/dist';
import type { EventID } from '@hs/room';
🤖 Prompt for AI Agents
In packages/core/src/events/m.room.member.ts around line 1, replace the runtime
import from 'packages/room/dist' with a type‑only import using the package alias
(e.g. import type { EventID } from 'room';) so the value is not imported at
runtime; ensure EventID is used only as a type or add a type-only import where
referenced.

@@ -1,3 +1,4 @@
import { EventID } from 'packages/room/dist';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Use a type‑only import for EventID to avoid runtime dependency on 'packages/room/dist'.

Prevents accidental runtime import under verbatimModuleSyntax/ESM.

-import { EventID } from 'packages/room/dist';
+import type { EventID } from 'packages/room/dist';

Please confirm tsconfig has either importsNotUsedAsValues: "remove" or that you’re using type‑only imports repo‑wide to avoid runtime loads of types. Run:


🏁 Script executed:

#!/bin/bash
fd -a 'tsconfig*.json' | xargs -I{} jq -r '.compilerOptions.importsNotUsedAsValues?, .compilerOptions.verbatimModuleSyntax?' {} 2>/dev/null

Length of output: 64


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf "PWD: %s\n\n" "$(pwd)"

echo "=== tsconfig files ==="
fd -HI -a 'tsconfig*.json' 2>/dev/null || true

for f in $(fd -HI -a 'tsconfig*.json' 2>/dev/null || true); do
  echo -e "\n--- $f ---"
  # print the two properties if present, otherwise indicate MISSING
  jq -r 'if .compilerOptions then
    "importsNotUsedAsValues: " + (.compilerOptions.importsNotUsedAsValues // "MISSING") + "\nverbatimModuleSyntax: " + ( (.compilerOptions.verbatimModuleSyntax // "MISSING") | tostring )
  else
    "no compilerOptions"
  end' "$f" 2>/dev/null || { echo "jq failed; printing first 200 chars:"; sed -n '1,40p' "$f"; }
done

echo -e "\n=== packages/core/src/events/m.room.name.ts ==="
if [ -f packages/core/src/events/m.room.name.ts ]; then
  sed -n '1,200p' packages/core/src/events/m.room.name.ts
else
  echo "file not found at that exact path; searching for matching files..."
  fd -HI 'm.room.name.ts' || true
fi

echo -e "\n=== Search for EventID import patterns ==="
rg -n --hidden --no-ignore --glob '!node_modules' "import\s+type\s*\{[^}]*\bEventID\b[^}]*\}" -S || true
rg -n --hidden --no-ignore --glob '!node_modules' "import\s*\{[^}]*\bEventID\b[^}]*\}" -S || true

echo -e "\n=== Search for imports from packages/room/dist ==="
rg -n --hidden --no-ignore --glob '!node_modules' "from\s+['\"]packages/room/dist['\"]" -S || true

echo -e "\n=== package.json typescript version (if present) ==="
if [ -f package.json ]; then
  jq -r '.devDependencies.typescript // .dependencies.typescript // "missing"' package.json 2>/dev/null || true
else
  echo "package.json not found at repo root"
fi

Length of output: 7223


Use a type-only import for EventID (avoid runtime dependency on 'packages/room/dist')

packages/core/src/events/m.room.name.ts currently uses a value import and no repo tsconfig sets importsNotUsedAsValues or verbatimModuleSyntax, so this will be emitted at runtime. Replace with a type-only import or set importsNotUsedAsValues: "remove" in the relevant tsconfig.

-import { EventID } from 'packages/room/dist';
+import type { EventID } from 'packages/room/dist';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { EventID } from 'packages/room/dist';
import type { EventID } from 'packages/room/dist';
🤖 Prompt for AI Agents
In packages/core/src/events/m.room.name.ts around line 1, the current import
"import { EventID } from 'packages/room/dist';" is a value import causing a
runtime dependency; change it to a type-only import by replacing it with "import
type { EventID } from 'packages/room/dist';" so TypeScript emits no runtime
require, or alternatively set the relevant tsconfig to include
"importsNotUsedAsValues": "remove" (or enable "verbatimModuleSyntax") in the
project that compiles this file.

import { createLogger } from '@hs/core';
import { generateId } from '@hs/core';
import { Pdu } from '@hs/room';
import { EventID, Pdu } from '@hs/room';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🧩 Analysis chain

Align EventID branding across the fetcher inputs, outputs, and locals.

You accept EventID[] but still use plain string for IDs internally and in the return type. This weakens type safety and can hide bugs in equality checks.

Suggested updates (outside selected lines):

export interface FetchedEvents {
  events: { eventId: EventID; event: Pdu }[];
  missingEventIds: EventID[];
}

const localEvents: { eventId: EventID; event: Pdu }[] = [];

private async fetchEventsFromFederation(
  eventIds: EventID[],
  targetServerName: string,
): Promise<Pdu[]> { /* ... */ }

Also confirm EventRepository.findByIds(...) and FederationService.getEvent(...) accept EventID:

Also applies to: 28-28


🏁 Script executed:

#!/bin/bash
rg -nP -C2 --type=ts '\bfindByIds\s*\('
rg -nP -C2 --type=ts '\bgetEvent\s*\('

Length of output: 3223


Align EventID branding across the fetcher inputs, outputs, and locals.

Fetcher accepts EventID[] but uses plain string for local IDs and the federation call; convert internals and signatures to EventID to restore type safety.

  • Change localEvents and fetcher return types in packages/federation-sdk/src/services/event-fetcher.service.ts (around line 39 and call sites at ~106–109) from string to EventID.
  • Change federation service signature in packages/federation-sdk/src/services/federation.service.ts (line ~151) to accept eventId: EventID and update call sites.
  • Confirm/ensure EventRepository.findByIds(...) accepts EventID (used in event.service.ts / state.service.ts); update if necessary.

Suggested edits:

export interface FetchedEvents {
  events: { eventId: EventID; event: Pdu }[];
  missingEventIds: EventID[];
}

const localEvents: { eventId: EventID; event: Pdu }[] = [];

private async fetchEventsFromFederation(
  eventIds: EventID[],
  targetServerName: string,
): Promise<Pdu[]> { /* ... */ }

async getEvent(domain: string, eventId: EventID): Promise<Pdu> { /* ... */ }
🤖 Prompt for AI Agents
In packages/federation-sdk/src/services/event-fetcher.service.ts around line 4
and particularly near line 39 and call sites ~106–109, the code uses plain
string for local event IDs and federation calls while the module imports
EventID; change all internal locals and signatures to use EventID (e.g.
localEvents array item type, FetchedEvents.missingEventIds, and fetcher return
types). Also update the federation service signature in
packages/federation-sdk/src/services/federation.service.ts (around line ~151) to
accept eventId: EventID and update every call site to pass an EventID, and
verify EventRepository.findByIds(...) (used in event.service.ts /
state.service.ts) accepts EventID[] and change its signature/usage if necessary
so types line up end-to-end. Ensure imports remain correct and adjust any
downstream typings/returns (e.g. fetchEventsFromFederation return type and
getEvent parameter/return types) to maintain type safety.

@@ -1,5 +1,5 @@
import { ProfilesService } from '@hs/federation-sdk';
import { type RoomVersion } from '@hs/room';
import { EventID, type RoomVersion } from '@hs/room';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🧩 Analysis chain

Avoid unsafe casts; validate/cast to EventID[] at the boundary.

Prefer schema-based parsing over as to keep branding intact.

-import { EventID, type RoomVersion } from '@hs/room';
+import { type EventID, type RoomVersion, eventIdSchema } from '@hs/room';
...
- body.earliest_events as EventID[],
- body.latest_events as EventID[],
+ eventIdSchema.array().parse(body.earliest_events),
+ eventIdSchema.array().parse(body.latest_events),

Also applies to: 106-108


🏁 Script executed:

#!/bin/bash
# Find remaining deep imports and unsafe EventID casts.
rg -nP "from ['\"]/?.*packages/room/dist['\"]"
rg -nP "\bas\s+EventID\[\]" -n
rg -nP "\beventId\s*:\s*string\b" --type=ts -n
rg -nP "tombstone_event_id\s*:\s*string\b" --type=ts -n

Length of output: 3108


Replace unsafe as EventID[] casts with schema parsing and remove deep 'packages/room/dist' imports

Use a schema (e.g., eventIdSchema.array().parse(...)) at API boundaries and import the schema from '@hs/room' instead of deep 'packages/room/dist'.

Files to fix (precise pointers):

  • Deep imports to replace with public export (@hs/room):
    • packages/federation-sdk/src/services/missing-event.service.ts:3
    • packages/core/src/utils/generateId.ts:2
    • packages/core/src/events/m.room.history_visibility.ts:1
    • packages/core/src/events/m.room.redaction.ts:1
    • packages/core/src/events/m.room.name.ts:1
    • packages/core/src/events/m.room.tombstone.ts:1
    • packages/core/src/events/m.room.power_levels.ts:1
    • packages/core/src/events/m.room.member.ts:1
    • packages/core/src/events/m.room.join_rules.ts:1
    • packages/core/src/events/m.room.guest_access.ts:1
  • Unsafe as EventID[] casts to replace with parsing:
    • packages/federation-sdk/src/services/state.service.ts:778
    • packages/federation-sdk/src/services/room.service.ts:1054
    • packages/homeserver/src/controllers/federation/profiles.controller.ts:106-107
    • packages/room/src/manager/v3.ts:20
    • packages/room/src/state_resolution/definitions/algorithm/v2.ts:107
    • packages/core/src/events/m.room.power_levels.ts:74

Suggested minimal change (example):

-import { EventID, type RoomVersion } from '@hs/room';
+import { type EventID, type RoomVersion, eventIdSchema } from '@hs/room';
...
- body.earliest_events as EventID[],
- body.latest_events as EventID[],
+ eventIdSchema.array().parse(body.earliest_events),
+ eventIdSchema.array().parse(body.latest_events),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { EventID, type RoomVersion } from '@hs/room';
import { type EventID, type RoomVersion, eventIdSchema } from '@hs/room';
...
eventIdSchema.array().parse(body.earliest_events),
eventIdSchema.array().parse(body.latest_events),
🤖 Prompt for AI Agents
In packages/homeserver/src/controllers/federation/profiles.controller.ts (fix
around the import at top and the usage at lines ~106-107), remove any deep
imports from 'packages/room/dist' and import the public schema/export from
'@hs/room' instead; replace unsafe "as EventID[]" casts by validating/parsing
incoming values with eventIdSchema.array().parse(...) (import eventIdSchema from
'@hs/room'), and use the parsed EventID[] result for downstream logic; ensure
types still reference EventID/RoomVersion from '@hs/room' only and propagate
parse errors appropriately (throw/return a 4xx) at the API boundary.

@@ -1,4 +1,5 @@
import { ConfigService, EventService } from '@hs/federation-sdk';
import { EventID } from '@hs/room';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Import eventIdSchema (not just EventID) for boundary validation.

Apply:

-import { EventID } from '@hs/room';
+import { EventID, eventIdSchema } from '@hs/room';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { EventID } from '@hs/room';
import { EventID, eventIdSchema } from '@hs/room';
🤖 Prompt for AI Agents
In packages/homeserver/src/controllers/federation/transactions.controller.ts
around line 2, the current import only pulls EventID from '@hs/room' but the
file needs eventIdSchema for boundary validation; update the import to include
eventIdSchema (e.g., import { EventID, eventIdSchema } from '@hs/room') and then
use eventIdSchema where incoming event IDs are validated so boundary validation
uses the canonical schema.

Comment on lines +47 to +49
const eventData = await eventService.getEventById(
params.eventId as EventID,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not cast params.eventId; validate and brand it.

Unsafe cast can pass invalid IDs into the service. Validate with eventIdSchema and return 400 on failure.

Apply:

-				const eventData = await eventService.getEventById(
-					params.eventId as EventID,
-				);
+				const parsed = eventIdSchema.safeParse(params.eventId);
+				if (!parsed.success) {
+					set.status = 400;
+					return { errcode: 'M_INVALID_PARAM', error: 'Invalid event ID' };
+				}
+				const eventData = await eventService.getEventById(parsed.data);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const eventData = await eventService.getEventById(
params.eventId as EventID,
);
const parsed = eventIdSchema.safeParse(params.eventId);
if (!parsed.success) {
set.status = 400;
return { errcode: 'M_INVALID_PARAM', error: 'Invalid event ID' };
}
const eventData = await eventService.getEventById(parsed.data);
🤖 Prompt for AI Agents
In packages/homeserver/src/controllers/federation/transactions.controller.ts
around lines 47 to 49, do not cast params.eventId; instead validate it using
eventIdSchema and, on success, use the branded value as EventID when calling
eventService.getEventById; if validation fails return a 400 response (with an
appropriate error message) and do not call the service. Ensure you replace the
unsafe cast with the validated/branched value and keep error handling consistent
with other controller validations.

Comment on lines 83 to 85
export interface EventStore {
getEvents(eventIds: string[]): Promise<PersistentEventBase[]>;
getEvents(eventIds: EventID[]): Promise<PersistentEventBase[]>;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🧩 Analysis chain

API shift acknowledged: EventStore.getEvents now takes EventID[].

This aligns the state resolution APIs with branded IDs.

Run to locate remaining string[] signatures:


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts -C2 '\bgetEvents\s*\(\s*eventIds\s*:\s*string\[\]'

Length of output: 1076


Update remaining test mocks: change getEvents(eventIds: string[]) → EventID[]

Two test mocks still declare getEvents with string[] — update their signatures and any related Maps/types to EventID to match the EventStore API.

  • packages/room/src/authorizartion-rules/rules.spec.ts — async getEvents(eventIds: string[]): Promise<PersistentEventBase[]> (line ~16)
  • packages/room/src/state_resolution/definitions/algorithm/v2.spec.ts — async getEvents(eventIds: string[]): Promise<PersistentEventBase[]> (line ~17)

Example change: async getEvents(eventIds: EventID[]): Promise<PersistentEventBase[]> { ... } (also update Map<string, ...> → Map<EventID, ...> where applicable).

🤖 Prompt for AI Agents
packages/room/src/state_resolution/definitions/definitions.ts lines 83-85:
Update test mocks in the two referenced spec files to match the EventStore
interface by changing getEvents parameter types from string[] to EventID[] and
any related Maps/keys to use EventID instead of string; specifically edit
packages/room/src/authorizartion-rules/rules.spec.ts (around line ~16) and
packages/room/src/state_resolution/definitions/algorithm/v2.spec.ts (around line
~17) to declare async getEvents(eventIds: EventID[]):
Promise<PersistentEventBase[]> { ... } and change Map<string, ...> →
Map<EventID, ...> or other types/looksups that use string keys so all signatures
and usages align with EventID.

@ggazzo ggazzo marked this pull request as ready for review September 16, 2025 18:05
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/federation-sdk/src/services/state.service.ts (1)

376-379: Tighten internal cache/key types to EventID.

Minor type hygiene: cache and local arrays can use EventID/PersistentEventBase to avoid any[] widening.

-  const cache = new Map<string, PersistentEventBase>();
+  const cache = new Map<EventID, PersistentEventBase>();
   return {
-    getEvents: async (eventIds: EventID[]): Promise<PersistentEventBase[]> => {
-      const events = [];
-      const toFind = [];
+    getEvents: async (eventIds: EventID[]): Promise<PersistentEventBase[]> => {
+      const events: PersistentEventBase[] = [];
+      const toFind: EventID[] = [];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 56a8f80 and a01f95f.

📒 Files selected for processing (5)
  • packages/federation-sdk/src/services/staging-area.service.ts (3 hunks)
  • packages/federation-sdk/src/services/state.service.ts (7 hunks)
  • packages/room/src/manager/event-wrapper.ts (2 hunks)
  • packages/room/src/manager/v3.ts (4 hunks)
  • packages/room/src/types/_common.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/room/src/manager/event-wrapper.ts
  • packages/room/src/manager/v3.ts
  • packages/room/src/types/_common.ts
🧰 Additional context used
🧬 Code graph analysis (2)
packages/federation-sdk/src/services/state.service.ts (4)
packages/room/src/manager/v3.ts (1)
  • eventId (22-34)
packages/room/src/types/_common.ts (2)
  • EventID (8-8)
  • StateMapKey (10-10)
packages/room/src/manager/room-state.ts (1)
  • RoomState (15-154)
packages/room/src/manager/event-wrapper.ts (1)
  • event (102-112)
packages/federation-sdk/src/services/staging-area.service.ts (1)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
🔇 Additional comments (9)
packages/federation-sdk/src/services/staging-area.service.ts (3)

6-6: Type-only import is correct.

Importing EventID as a type avoids runtime impact and matches the PR goal.


171-175: Reaction 'm.relates_to.event_id' changed to EventID — verify consumers expecting string

File: packages/federation-sdk/src/services/staging-area.service.ts (lines 171–175)

Repo search returned no matches for consumers typed as string. Absence of matches is not proof — manually verify external/third-party callers, serialization/deserialization paths, and any runtime code casting or treating event_id as string.


151-154: Annotate m.relates_to.event_id as EventID — verify downstream consumers.

Location: packages/federation-sdk/src/services/staging-area.service.ts (lines 151–154). Typing event_id as EventID tightens the emitter payload; automated search produced no matches for consumers declaring event_id: string, so manual verification is required. Confirm all downstream consumers/handlers of homeserver.matrix.message and any processors of m.relates_to accept EventID or update their types.

packages/federation-sdk/src/services/state.service.ts (6)

3-3: EventID import at source — aligned with branding.

Consistent with room’s branded ID schema.


271-273: Param now Map<StateMapKey, EventID> — consistent with the previous change.


348-351: getFullRoomStateBeforeEvent2(eventId: EventID) — API consistency looks good.


776-779: Passing EventID[] to store.getEvents — correct.

Matches the new store signature; no runtime impact.


118-121: findStateAtEvent now takes EventID — verify callers pass an EventID

  • Call sites found:
    • packages/homeserver/src/controllers/internal/room.controller.ts:192 — passes eventId as EventID (OK).
    • packages/federation-sdk/src/services/state.service.ts:349 — internal call with eventId: EventID (OK).
    • packages/federation-sdk/src/services/state.service.ts:631 — calls this.findStateAtEvent(lastEvent._id, 'always') — CONFIRM lastEvent._id is an EventID (not a DB _id); if not, use the event's EventID field or cast.
    • packages/federation-sdk/src/services/event.service.ts:696 and 755 — pass eventId — VERIFY the originating variable is typed as EventID.
  • Action: confirm types at the listed locations and update any caller that passes a DB id or wrong type to an EventID (or add an explicit cast).

254-258: Return type is correct — no caller changes required.
buildFullRoomStateFromEvents is only invoked inside packages/federation-sdk/src/services/state.service.ts and its callers (buildFullRoomStateFromStateMapping, buildFullRoomStateFromStateMappings, buildFullRoomStateStoredEvents) already use Map<StateMapKey, EventID>.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants