85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
import { $replica } from "~/db.server";
|
|
import { env } from "~/env.server";
|
|
import { logger } from "~/services/logger.server";
|
|
import { BoundedTtlCache } from "~/services/realtime/boundedTtlCache";
|
|
import { singleton } from "~/utils/singleton";
|
|
import { FEATURE_FLAG } from "~/v3/featureFlags";
|
|
import { makeFlag } from "~/v3/featureFlags.server";
|
|
import { isSplitEnabled } from "./splitMode.server";
|
|
|
|
export type RunIdMintKind = "cuid" | "runOpsId";
|
|
|
|
type MintKindDeps = {
|
|
masterEnabled: boolean;
|
|
splitEnabled: () => Promise<boolean>;
|
|
// Receives the orgId + the (optional) already-loaded org feature flags. When
|
|
// orgFeatureFlags is provided, the implementation must NOT read the DB for them.
|
|
flag: (orgId: string, orgFeatureFlags: unknown | undefined) => Promise<RunIdMintKind>;
|
|
};
|
|
|
|
// PURE CORE — no env import; tests drive this directly. Gate order is load-bearing:
|
|
// master switch → split gate → per-org flag, short-circuiting at the first OFF.
|
|
export async function computeRunIdMintKind(
|
|
environment: { organizationId: string; id: string; orgFeatureFlags?: unknown },
|
|
deps: MintKindDeps
|
|
): Promise<RunIdMintKind> {
|
|
if (!deps.masterEnabled) return "cuid";
|
|
if (!(await deps.splitEnabled())) return "cuid";
|
|
try {
|
|
return await deps.flag(environment.organizationId, environment.orgFeatureFlags);
|
|
} catch (error) {
|
|
logger.error("[runOpsMintKind] flag read failed; minting cuid (fail-safe)", { error });
|
|
return "cuid";
|
|
}
|
|
}
|
|
|
|
// ENV-BOUND wrapper — the only place env/$replica/isSplitEnabled are read.
|
|
const flagFn = singleton("runOpsMintFlag", () => makeFlag($replica));
|
|
const mintCache = singleton(
|
|
"runOpsMintCache",
|
|
() =>
|
|
new BoundedTtlCache<RunIdMintKind>(
|
|
env.RUN_OPS_MINT_FLAG_CACHE_TTL_MS,
|
|
env.RUN_OPS_MINT_FLAG_CACHE_MAX_ENTRIES
|
|
)
|
|
);
|
|
|
|
export async function resolveRunIdMintKind(environment: {
|
|
organizationId: string;
|
|
id: string;
|
|
// Pass environment.organization.featureFlags from the trigger call site.
|
|
orgFeatureFlags?: unknown;
|
|
}): Promise<RunIdMintKind> {
|
|
return computeRunIdMintKind(environment, {
|
|
masterEnabled: env.RUN_OPS_MINT_ENABLED,
|
|
splitEnabled: isSplitEnabled,
|
|
flag: async (orgId, orgFeatureFlags) => {
|
|
// The cache stores only "cuid"|"runOpsId" (never undefined), so the cache's
|
|
// "stored-undefined == miss" caveat never applies here.
|
|
const cached = mintCache.get(orgId);
|
|
if (cached !== undefined) return cached;
|
|
|
|
// Hot-path pass-through: use the org flags the authenticated environment already
|
|
// carries; only fall back to a DB read when the caller did NOT pass them (non-trigger
|
|
// callers). The trigger path always passes them, so it never issues this findFirst.
|
|
const overrides =
|
|
orgFeatureFlags !== undefined
|
|
? orgFeatureFlags
|
|
: (
|
|
await $replica.organization.findFirst({
|
|
where: { id: orgId },
|
|
select: { featureFlags: true },
|
|
})
|
|
)?.featureFlags;
|
|
|
|
const kind = await flagFn({
|
|
key: FEATURE_FLAG.runOpsMintKind,
|
|
defaultValue: "cuid",
|
|
overrides: (overrides as Record<string, unknown>) ?? {},
|
|
});
|
|
mintCache.set(orgId, kind);
|
|
return kind;
|
|
},
|
|
});
|
|
}
|