Skip to content

Commit 0136d39

Browse files
committed
refactor(webapp): route the global flags write through the transaction helper
Use the $transaction helper from ~/db.server instead of calling client.$transaction directly, so the write gets tracing and infra-error boundary logging. The helper is callback-only, so the batched upserts become sequential statements inside one interactive transaction, and an undefined result is treated as a failure rather than a silent no-op.
1 parent ccbe4e9 commit 0136d39

1 file changed

Lines changed: 24 additions & 16 deletions

File tree

apps/webapp/app/v3/featureFlags.server.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type z } from "zod";
22
import type { PrismaClient } from "@trigger.dev/database";
3-
import { boundedIn, prisma, type PrismaClientOrTransaction } from "~/db.server";
3+
import { $transaction, boundedIn, prisma, type PrismaClientOrTransaction } from "~/db.server";
44
import {
55
FEATURE_FLAG,
66
type FeatureFlagCatalogSchema,
@@ -240,28 +240,36 @@ export async function replaceGlobalFeatureFlags(
240240
}
241241
): Promise<void> {
242242
const canDeleteLocked = params.unlockLockedFlags && !params.isManagedCloud;
243-
const upsertOps: ReturnType<typeof client.featureFlag.upsert>[] = [];
243+
const toUpsert: { key: FeatureFlagKey; value: unknown }[] = [];
244244
const keysToDelete: string[] = [];
245245

246246
for (const key of params.catalogKeys) {
247247
if (key in params.requestedFlags) {
248-
const value = params.requestedFlags[key];
249-
upsertOps.push(
250-
client.featureFlag.upsert({
251-
where: { key },
252-
create: { key, value: value as any },
253-
update: { value: value as any },
254-
})
255-
);
248+
toUpsert.push({ key, value: params.requestedFlags[key] });
256249
} else if (canDeleteLocked || !GLOBAL_LOCKED_FLAGS.includes(key)) {
257250
keysToDelete.push(key);
258251
}
259252
}
260253

261-
await client.$transaction([
262-
...upsertOps,
263-
...(keysToDelete.length > 0
264-
? [client.featureFlag.deleteMany({ where: { key: { in: boundedIn(keysToDelete) } } })]
265-
: []),
266-
]);
254+
const applied = await $transaction(client, "replaceGlobalFeatureFlags", async (tx) => {
255+
for (const { key, value } of toUpsert) {
256+
await tx.featureFlag.upsert({
257+
where: { key },
258+
create: { key, value: value as any },
259+
update: { value: value as any },
260+
});
261+
}
262+
263+
if (keysToDelete.length > 0) {
264+
await tx.featureFlag.deleteMany({ where: { key: { in: boundedIn(keysToDelete) } } });
265+
}
266+
267+
return true;
268+
});
269+
270+
// The helper resolves undefined instead of throwing when Prisma errors are swallowed. This
271+
// write deletes flags, so treat a transaction that did not run as a failure the caller sees.
272+
if (!applied) {
273+
throw new Error("replaceGlobalFeatureFlags: transaction did not complete");
274+
}
267275
}

0 commit comments

Comments
 (0)