Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/utils/services/.server/featureFlagsRequest.server.ts
T

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);
}