Skip to content

Commit c99b62a

Browse files
committed
fix: cache race
1 parent 51fbd8a commit c99b62a

File tree

3 files changed

+45
-53
lines changed

3 files changed

+45
-53
lines changed

packages/rpc/src/routers/flags.ts

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { websitesApi } from "@databuddy/auth";
22
import {
33
and,
4-
db,
54
desc,
65
eq,
76
flags,
@@ -10,19 +9,17 @@ import {
109
isNull,
1110
targetGroups,
1211
} from "@databuddy/db";
13-
import {
14-
createDrizzleCache,
15-
invalidateCacheableWithArgs,
16-
redis,
17-
} from "@databuddy/redis";
12+
import { createDrizzleCache, redis } from "@databuddy/redis";
1813
import {
1914
flagFormSchema,
2015
userRuleSchema,
2116
variantSchema,
2217
} from "@databuddy/shared/flags";
2318
import {
19+
getScope,
2420
getScopeCondition,
2521
handleFlagUpdateDependencyCascading,
22+
invalidateFlagCache,
2623
} from "@databuddy/shared/flags/utils";
2724
import { ORPCError } from "@orpc/server";
2825
import { randomUUIDv7 } from "bun";
@@ -35,9 +32,6 @@ import { getCacheAuthContext } from "../utils/cache-keys";
3532
const flagsCache = createDrizzleCache({ redis, namespace: "flags" });
3633
const CACHE_DURATION = 60;
3734

38-
const getScope = (websiteId?: string, organizationId?: string) =>
39-
websiteId ? `website:${websiteId}` : `org:${organizationId}`;
40-
4135
const authorizeScope = async (
4236
context: Context,
4337
websiteId?: string,
@@ -64,41 +58,6 @@ const authorizeScope = async (
6458
}
6559
};
6660

67-
const invalidateFlagCache = async (
68-
id: string,
69-
websiteId?: string | null,
70-
organizationId?: string | null
71-
) => {
72-
const scope = getScope(websiteId ?? undefined, organizationId ?? undefined);
73-
74-
// Get the flag details to invalidate public API cache
75-
const flag = await db
76-
.select({ key: flags.key })
77-
.from(flags)
78-
.where(eq(flags.id, id))
79-
.limit(1);
80-
81-
const invalidations: Promise<unknown>[] = [
82-
flagsCache.invalidateByTables(["flags"]),
83-
flagsCache.invalidateByKey(`byId:${id}:${scope}`),
84-
];
85-
86-
if (flag[0]?.key) {
87-
const clientId = websiteId || organizationId;
88-
if (clientId) {
89-
invalidations.push(
90-
invalidateCacheableWithArgs("flag", [flag[0].key, clientId])
91-
);
92-
93-
invalidations.push(
94-
invalidateCacheableWithArgs("flags-client", [clientId])
95-
);
96-
}
97-
}
98-
99-
await Promise.all(invalidations);
100-
};
101-
10261
const listFlagsSchema = z
10362
.object({
10463
websiteId: z.string().optional(),
@@ -264,7 +223,7 @@ function sanitizeFlagForDemo<T extends FlagWithTargetGroups>(flag: T): T {
264223
...flag,
265224
rules: Array.isArray(flag.rules) && flag.rules.length > 0 ? [] : flag.rules,
266225
targetGroups: flag.targetGroups?.map(
267-
(group: { rules?: unknown; [key: string]: unknown }) => ({
226+
(group: { rules?: unknown;[key: string]: unknown }) => ({
268227
...group,
269228
rules:
270229
Array.isArray(group.rules) && group.rules.length > 0

packages/shared/src/flags/utils.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,56 @@ import {
77
inArray,
88
isNull,
99
} from "@databuddy/db";
10-
import { createDrizzleCache, redis } from "@databuddy/redis";
10+
import {
11+
createDrizzleCache,
12+
invalidateCacheableWithArgs,
13+
redis,
14+
} from "@databuddy/redis";
1115
import { logger } from "@/utils/logger";
1216

1317
const flagsCache = createDrizzleCache({ redis, namespace: "flags" });
1418

15-
const getScope = (websiteId?: string | null, organizationId?: string | null) =>
16-
websiteId ? `website:${websiteId}` : `org:${organizationId}`;
19+
export const getScope = (
20+
websiteId?: string | null,
21+
organizationId?: string | null
22+
) => (websiteId ? `website:${websiteId}` : `org:${organizationId}`);
1723

1824
export const invalidateFlagCache = async (
1925
id: string,
2026
websiteId?: string | null,
21-
organizationId?: string | null
27+
organizationId?: string | null,
28+
flagKey?: string
2229
) => {
30+
const clientId = websiteId || organizationId;
31+
32+
let key = flagKey;
33+
if (!key && clientId) {
34+
const result = await db
35+
.select({ key: flags.key })
36+
.from(flags)
37+
.where(eq(flags.id, id))
38+
.limit(1);
39+
key = result[0]?.key;
40+
}
41+
2342
const scope = getScope(websiteId, organizationId);
24-
await Promise.allSettled([
43+
const invalidations: Promise<unknown>[] = [
2544
flagsCache.invalidateByTables(["flags"]),
2645
flagsCache.invalidateByKey(`byId:${id}:${scope}`),
27-
]);
46+
];
47+
48+
if (clientId) {
49+
if (key) {
50+
invalidations.push(
51+
invalidateCacheableWithArgs("flag", [key, clientId])
52+
);
53+
}
54+
invalidations.push(
55+
invalidateCacheableWithArgs("flags-client", [clientId])
56+
);
57+
}
58+
59+
await Promise.allSettled(invalidations);
2860
};
2961

3062
export const getScopeCondition = (
@@ -159,7 +191,8 @@ export async function handleFlagUpdateDependencyCascading(
159191
return invalidateFlagCache(
160192
flagUpdate.id,
161193
affectedFlag?.websiteId,
162-
affectedFlag?.organizationId
194+
affectedFlag?.organizationId,
195+
flagUpdate.key
163196
);
164197
})
165198
);

packages/shared/tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)