86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
/**
|
|
* Request-bound bridge into the v2 evaluator.
|
|
*
|
|
* The legacy {@link FeatureFlagsService.hasFeatureFlag} accepts
|
|
* `(request, params, flagName)`. The v2 engine accepts an
|
|
* {@link EvaluationContext}. This helper assembles the context from
|
|
* the request (tenantId, userId) and delegates to
|
|
* `evaluateFlagForContext`, so existing call-sites can switch
|
|
* imports without changing their signatures.
|
|
*
|
|
* Returns `{ enabled, variant?, reason }` — call-sites that only
|
|
* care about the boolean use the convenience `isFeatureFlagEnabled`
|
|
* wrapper. Existing v1 routes that already build their own context
|
|
* via `FeatureFlagsService.hasFeatureFlag` keep working in parallel;
|
|
* v2 is opt-in.
|
|
*/
|
|
|
|
import { Params } from "react-router";
|
|
import type { EvaluateFlagResult } from "@toolctrl/feature-flags";
|
|
import { evaluateFlagForContext, evaluateFlagsForContext } from "./featureFlagEvaluator.server";
|
|
import { getAllFeatureFlags } from "~/modules/featureFlags/db/featureFlags.db.server";
|
|
import { getUserInfo } from "~/utils/session.server";
|
|
import { getTenantIdOrNull } from "~/utils/services/.server/urlService";
|
|
|
|
export interface RequestContextInput {
|
|
request: Request;
|
|
params: Params;
|
|
}
|
|
|
|
export async function evaluateFlagForRequest(
|
|
flagName: string,
|
|
{ request, params }: RequestContextInput,
|
|
): Promise<EvaluateFlagResult> {
|
|
const [tenantId, userInfo] = await Promise.all([
|
|
getTenantIdOrNull({ request, params }),
|
|
getUserInfo(request),
|
|
]);
|
|
return evaluateFlagForContext(flagName, {
|
|
flagName,
|
|
tenantId: tenantId ?? null,
|
|
userId: userInfo.userId || null,
|
|
});
|
|
}
|
|
|
|
/** Boolean shortcut for callers migrating from `hasFeatureFlag`. */
|
|
export async function isFeatureFlagEnabled(
|
|
flagName: string,
|
|
input: RequestContextInput,
|
|
): Promise<boolean> {
|
|
const result = await evaluateFlagForRequest(flagName, input);
|
|
return result.enabled;
|
|
}
|
|
|
|
/**
|
|
* SSR-hydration helper — return the names of every flag whose v2
|
|
* evaluation resolves `enabled` for the current request context.
|
|
*
|
|
* Replaces the v1 `FeatureFlagsService.getCurrentFeatureFlags` which
|
|
* was scoped + filtered through `FeatureFlagsFiltersService` (a
|
|
* separate filter engine that ran *outside* the v2 evaluator). The v2
|
|
* port skips the legacy filter table: callers are responsible for
|
|
* porting any per-flag filter rows into v2 rules in
|
|
* `FeatureFlag.rules` (see `docs/improvement-plan/...`). Until that
|
|
* data migration lands, results reflect the flag's bare
|
|
* `enabled=true` boolean modulo v2 rule evaluation.
|
|
*/
|
|
export async function getCurrentEnabledFlagNames(input: RequestContextInput): Promise<string[]> {
|
|
const [tenantId, userInfo, flags] = await Promise.all([
|
|
getTenantIdOrNull(input),
|
|
getUserInfo(input.request),
|
|
getAllFeatureFlags(),
|
|
]);
|
|
const names = flags
|
|
.map((f) => f.name ?? f.key ?? "")
|
|
.filter((n) => n.length > 0);
|
|
if (names.length === 0) return [];
|
|
const results = await evaluateFlagsForContext(names, {
|
|
flagName: "*",
|
|
tenantId: tenantId ?? null,
|
|
userId: userInfo.userId || null,
|
|
});
|
|
return Object.entries(results)
|
|
.filter(([, r]) => r.enabled)
|
|
.map(([n]) => n);
|
|
}
|