@@ -12,6 +12,7 @@ import { ApiKeyService } from "../auth/api-keys";
1212import { UserStoreService } from "../auth/context" ;
1313import type { Session } from "../auth/middleware" ;
1414import { WorkOSClient } from "../auth/workos" ;
15+ import { WorkOsMirror , mirrorMembershipFromWorkOs } from "../auth/workos-mirror" ;
1516import { ORG_SELECTOR_HEADER , authorizeOrganizationSelector } from "../auth/organization" ;
1617import { AutumnService } from "../extensions/billing/service" ;
1718import { forkReportMemberSeats } from "../extensions/billing/member-seats" ;
@@ -50,7 +51,7 @@ export class AccountCaller extends Context.Service<
5051// (me / API keys) and `org/handlers.ts` (members / roles / invite / role /
5152// name). Native WorkOS / store failures are mapped at this boundary onto the
5253// neutral account errors so the shared UI sees one shape:
53- // WorkOSError | UserStoreError | ApiKeyManagementError → AccountError
54+ // WorkOSError | UserStoreError | ApiKeyManagementError | WorkOsMirrorError → AccountError
5455// no organization in session → AccountNoOrganization
5556// not-an-admin / over-seat-limit / not-allowed → AccountForbidden
5657// ---------------------------------------------------------------------------
@@ -65,13 +66,17 @@ const toAccountError = () => Effect.fail(new AccountError({ message: "Account re
6566export const workosAccountProvider : Layer . Layer <
6667 AccountProvider ,
6768 never ,
68- WorkOSClient | UserStoreService | ApiKeyService | AutumnService | AccountCaller
69+ WorkOSClient | UserStoreService | WorkOsMirror | ApiKeyService | AutumnService | AccountCaller
6970> = Layer . effect ( AccountProvider ) (
7071 Effect . gen ( function * ( ) {
7172 const workos = yield * WorkOSClient ;
7273 const apiKeys = yield * ApiKeyService ;
7374 const autumn = yield * AutumnService ;
7475 const users = yield * UserStoreService ;
76+ // Membership writes below go to WorkOS FIRST (the authority), then are
77+ // written through to the local mirror so the member list and the seat
78+ // count read the change without waiting for the Events reconciler.
79+ const mirror = yield * WorkOsMirror ;
7580
7681 // The caller, resolved once per request by the cookie-only session
7782 // middleware (account-api.ts) — the same credential `SessionAuthLive`
@@ -365,6 +370,9 @@ export const workosAccountProvider: Layer.Layer<
365370 yield * workos
366371 . deleteOrgMembership ( membershipId )
367372 . pipe ( Effect . catchTag ( "WorkOSError" , toAccountError ) ) ;
373+ yield * mirror
374+ . deleteMembership ( membershipId )
375+ . pipe ( Effect . catchTag ( "WorkOsMirrorError" , toAccountError ) ) ;
368376 yield * forkReportMemberSeats ( org . id ) . pipe ( Effect . provideContext ( ctx ) ) ;
369377 return { success : true } ;
370378 } ) ,
@@ -374,9 +382,12 @@ export const workosAccountProvider: Layer.Layer<
374382 const { session, org } = yield * requireOrganization ( headers ) ;
375383 yield * requireAdmin ( session . accountId , org . id ) ;
376384 yield * assertMembershipInOrg ( org . id , membershipId ) ;
377- yield * workos
385+ const updated = yield * workos
378386 . updateOrgMembershipRole ( membershipId , roleSlug )
379387 . pipe ( Effect . catchTag ( "WorkOSError" , toAccountError ) ) ;
388+ yield * mirror
389+ . upsertMembership ( mirrorMembershipFromWorkOs ( updated ) )
390+ . pipe ( Effect . catchTag ( "WorkOsMirrorError" , toAccountError ) ) ;
380391 return { success : true } ;
381392 } ) ,
382393
0 commit comments