History

Keep seat reconciliation alive after the response

Rhys Sullivan committed 1 month ago

Commit 13693c61 parent 3fd28a52 files · +9 −4Browse files

Unified · Split

2 files
  1. apps/cloud/src/extensions/billing/member-seats.ts +5 −4
  2. apps/cloud/test-stubs/cloudflare-workers.ts +4 −0
apps/cloud/src/extensions/billing/member-seats.ts+5−4
@@ -3,6 +3,7 @@ · 2 unmodified lines
3// ---------------------------------------------------------------------------3// ---------------------------------------------------------------------------
4 4
5import { Effect } from "effect";5import { Effect } from "effect";
6import { waitUntil } from "cloudflare:workers";
6 7
7import { WorkOSClient } from "../../auth/workos";8import { WorkOSClient } from "../../auth/workos";
8import { AutumnService } from "./service";9import { AutumnService } from "./service";
@@ -38,9 +39,9 @@ export const reportMemberSeats = ( · 29 unmodified lines
38/**39/**
39 * Fork `reportMemberSeats` off the calling request, mirroring how execution40 * Fork `reportMemberSeats` off the calling request, mirroring how execution
40 * tracking is forked: billing must never stall or fail a user-facing41 * tracking is forked: billing must never stall or fail a user-facing
41 * request. Only boot-scoped services are captured (WorkOS + Autumn — no42 * request. Cloudflare owns the promise through waitUntil, so the recount can
42 * request-scoped resources), so the forked fiber cannot outlive anything it43 * finish after the response. Only boot-scoped WorkOS and Autumn services are
43 * depends on.44 * captured.
44 */45 */
45export const forkReportMemberSeats = (46export const forkReportMemberSeats = (
46 organizationId: string,47 organizationId: string,
@@ -48,6 +49,6 @@ export const forkReportMemberSeats = ( · 1 unmodified line
48 Effect.gen(function* () {49 Effect.gen(function* () {
49 const ctx = yield* Effect.context<WorkOSClient | AutumnService>();50 const ctx = yield* Effect.context<WorkOSClient | AutumnService>();
50 yield* Effect.sync(() => {51 yield* Effect.sync(() => {
51 Effect.runForkWith(ctx)(reportMemberSeats(organizationId));52 waitUntil(Effect.runPromiseWith(ctx)(reportMemberSeats(organizationId)));
52 });53 });
53 });54 });
apps/cloud/test-stubs/cloudflare-workers.ts+4−0
@@ -7,6 +7,10 @@ · 6 unmodified lines
7// from `process.env` into this stub at import time. Without this bridge7// from `process.env` into this stub at import time. Without this bridge
8// the test DbService would dial the default postgres port instead of the8// the test DbService would dial the default postgres port instead of the
9// PGlite socket server started by `scripts/test-globalsetup.ts`.9// PGlite socket server started by `scripts/test-globalsetup.ts`.
10import { vi } from "vitest";
11
12export const waitUntil = vi.fn<(work: Promise<unknown>) => void>();
13
10export const env: Record<string, unknown> = {14export const env: Record<string, unknown> = {
11 DATABASE_URL: process.env.DATABASE_URL,15 DATABASE_URL: process.env.DATABASE_URL,
12 WORKOS_API_KEY: process.env.WORKOS_API_KEY,16 WORKOS_API_KEY: process.env.WORKOS_API_KEY,